I'm trying to get artifacts from CircleCI in Powershell and getting back an unfamiliar data format?
Powershell likes to auto-convert your API’s JSON response to a PSCustomObject. This normally would be what I want.
Here is an example of my attempts at getting clean data.
Add the necessary .NET assembly
Add-Type -AssemblyName System.Net.Http
Create the HttpClient object
$client = New-Object -TypeName System.Net.Http.Httpclient
Get the web content.
$task = $client.GetByteArrayAsync(“https://circleci.com/api/v1.1/project/$vcs_type/$username/$project/$build_number/artifacts?circle-token=$CIRCLE_TOKEN”)
Wait for the async call to finish
$task.wait();
results:
(({
:path “src./file1.txt”,
:pretty-path “src/file1.txt”,
:node-index 0,
:url “https://15-198716507-gh.circle-artifacts.com/0/src/file1.txt”
} {
:path “src/file2.txt”,
:pretty-path “src/file2.txt”,
:node-index 0,
:url “https://15-198716507-gh.circle-artifacts.com/0/src/file2.txt”
}…continued
As you can see this is not JSON or YAML. Let’s try the built-in PowerShell tools like Invoke-RestMethod.
Invoke-RESTMethod -uri https://circleci.com/api/v1.1/project/$vcs_type/$username/$project/$build_number/artifacts?circle-token=$CIRCLE_TOKEN -Method GET
**Output:**
({
:path “src/file1.txt”,
:pretty-path “src/file1.txt”,
:node-index 0,
:url “https://15-198716507-gh.circle-artifacts.com/0/src/file1.txt”
} {
:path “src/file2.txt”,
:pretty-path “src/file2.txt”,
:node-index 0,
:url “https://15-198716507-gh.circle-artifacts.com/0/src/file2.txt”
}…continued
Dang same output. I know from the Invoke-RestMethod documentation that PS sees JSON and auto converts it to a PS object. Maybe it's converting a data type I'm not familiar with? I found it odd that PowerShell was getting EDN type when every other attempt outside PowerShell was JSON.
Maybe they should have the API updated to reply to PS request with JSON by default.
What is wrong with PowerShell not getting JSON data?
It's EDN, didn't know this until CircleCI answered a question on this topic. So if you are using PowerShell to retrieve artifacts from CircleCI you definitely want to know this.
you need to pass a header specifying the data type returned.
(Accept: application/json)
A CircleCI Support member let me know that in PowerShell you have to specify an Accept header to receive the data in JSON. No wonder I'm getting weird output!
So trying again with the new accept JSON header we have this command below.
Working command to get data in JSON and have it auto-convert to a PSObject.
Invoke-RestMethod -Uri https://circleci.com/api/v1.1/project/$vcs_type/$username/$project/$build_number/artifacts?circle-token=$CIRCLE_TOKEN -Method GET -ContentType 'application/json' -UseBasicParsing -Header #{"Accept" = "application/json"}
OUTPUT
$response|select path,url
path
----
src.orig/file1.txt
src.orig/file2.txt
url
---
https://15-824975-gh.circle-artifacts.com/0/src.orig/file1.txt
https://15-824975-gh.circle-artifacts.com/0/src.orig/file2.txt
Using the PS commands Invoke-WebRequest/Invoke-RestMethod both will receive data in EDN format if you don't do the below. Yay now I can use the data as I see fit to download my artifacts.
Reply from CircleCI that got me the solution.
#burninmedia So what's being sent back is actually a data format called EDN. If you want to return JSON you'll need to pass a header specifying so (Accept: application/json). Thanks!
Here is a simple script I wrote to download all the artifacts. Please be sure you're setting the environment variables.
if ($USERNAME -eq $null) { write-host " please add required variable USERNAME" ;exit }
if ($VCS_TYPE -eq $null) { write-host " please add required variable VCS_TYPE" ;exit}
if ($CIRCLE_TOKEN -eq $null) { write-host " please add required variable CIRCLE_TOKEN" ;exit}
if ($BUILD_NUMBER -eq $null) { write-host " please add required variable BUILD_NUMBER" ;exit}
if ($PROJECT -eq $null) { write-host " please add required variable PROJECT" ;exit}
if ($BASEPATH -eq $null) { write-host " please add required variable BASEPATH" ;exit}
$response = Invoke-RestMethod -Uri https://circleci.com/api/v1.1/project/$VCS_TYPE/$USERNAME/$PROJECT/$BUILD_NUMBER/artifacts?circle-token=$CIRCLE_TOKEN -Method GET -ContentType 'application/json' -UseBasicParsing -Header #{"Accept" = "application/json"}
ForEach ($i in $response){
$PATH = $(Split-Path -Path "$($BASEPATH)\$($i.path)")
if (-Not ( Test-Path $PATH) ) {
write-host "Creating folder: $($PATH)"
New-Item -ItemType Directory -Force -Path "$($PATH)"
}
Write-Host "Saving artifact $($i.pretty_path) to file: $($BASEPATH)\$($i.path)"
Invoke-RestMethod "$($i.url)?circle-token=$($CIRCLE_TOKEN)" -UseBasicParsing -OutFile "$($BASEPATH)\$($i.path)"
}
Bash version
export CIRCLE_TOKEN=':your_token'
echo $(https://circleci.com/api/v1.1/project/$vcs-type/$username/$project/$build_number/artifacts?circle-token=$CIRCLE_TOKEN) > ./artifact_json
for ((i = 0 ; i <= $(jq -c '.[].url ' ./artifact_json|wc -l) ; i++));
do
path=$(jq -c ".[$i].path" ./artifact_json|tr -d '"');
url=$(jq -c ".[$i].url" ./artifact_json|tr -d '"');
pathdir=$(dirname "$path")
echo "URL: $url"
echo "path: $path"
echo "Pathdir: $pathdir"
[ -d $pathdir ] && mkdir -p "$pathdir" #check if folder exists if not mkdir
wget -o $path $url
done
rm ./artifact_json```
Related
I'm learning the powershell. Currently I have a tough requirement. I need to call an powershell script(ps1) in parallel from an powershell module(psm1). The ps1 task is like following
param(
[Parameter(Mandatory=$true)]
[String] $LogMsg,
[Parameter(Mandatory=$true)]
[String] $FilePath
)
Write-Output $LogMsg
$LogMsg | Out-File -FilePath $FilePath -Append
The FilePath is like "C:\Users\user\Documents\log\log1.log"
And in the psm1 file, I use the runspacepool to do async task. Like the following demo
$MaxRunspaces = 5
$RunspacePool = [runspacefactory]::CreateRunspacePool(1, $MaxRunspaces)
$RunspacePool.Open()
$Jobs = New-Object System.Collections.ArrayList
Write-Host $currentPath
Write-Host $lcmCommonPath
$Filenames = #("log1.log", "log2.log", "log3.log")
foreach ($File in $Filenames) {
Write-Host "Creating runspace for $File"
$PowerShell = [powershell]::Create()
$PowerShell.RunspacePool = $RunspacePool
$FilePath = -Join("C:\Users\user\Documents\log\",$File)
$PowerShell.AddScript("C:\Users\user\Documents\foo.ps1").AddArgument($FilePath) | Out-Null
$JobObj = New-Object -TypeName PSObject -Property #{
Runspace = $PowerShell.BeginInvoke()
PowerShell = $PowerShell
}
$Jobs.Add($JobObj) | Out-Null
}
But there are two serious problem.
Can't pass the parameters to ps1 file.
I just try to create the file path in the ps1 file side, it works and file created. But when I try to pass the argument from psm1 file. The files are not created. I also try to use script block and it can pass the parameters. But since my ps1 code is too large(The above is just part of it), using script block is unreal. I need a method to pass parameter to ps1 file.
Can't get write-host information in ps1 file while psm1 is still running
If the runspacepool has limitation for passing the parameters to ps1 file, is there any other solution to deal with the async task for powershell script? Thanks.
Can't pass the parameters to ps1 file.
Use AddParameter() instead of AddArgument() - this will allow you to bind the argument to a specific parameter by name:
$PowerShell.AddScript("C:\Users\user\Documents\foo.ps1").
AddParameter('FilePath', $FilePath).
AddParameter('LogMsg', 'Log Message goes here') | Out-Null
Can't get write-host information in ps1 file while psm1 is still running
Correct - you cannot get host output from a script not attached to the host application's default runspace - but if you're using PowerShell 5 or newer you can collect the resulting information from the $PowerShell instance and relay that if you want to:
# Register this event handler after creating `$PowerShell` but _before_ calling BeginInvoke()
Register-ObjectEvent -InputObject $PowerShell.Streams.Information -EventName DataAdded -SourceIdentifier 'WriteHostRecorded' -Action {
$recordIndex = $EventArgs.Index
$data = $PowerShell.Streams.Information[$recordIndex]
Write-Host "async task wrote '$data'"
}
I'm just starting out so I must be missing something.
Downloading each file works fine: I just don't know how to append to the same output file. Here's what I currently have.
Invoke-WebRequest -Uri "https://website.com/part1.bin" -OutFile "D:\stuff\bigfile.bin"
Invoke-WebRequest -Uri "https://website.com/part2.bin" -OutFile "D:\stuff\bigfile.bin"
Invoke-WebRequest -Uri "https://website.com/part3.bin" -OutFile "D:\stuff\bigfile.bin"
Any tips? Thanks.
Here's what I ended up using. Works for me!
# Requires Powershell 6.0+ because of AsByteStream
# Example array containing URLs to combine
$arrUrls = #('https://website.com/part1.bin','https://website.com/part2.bin','https://website.com/part3.bin')
$tempFile = "temp.bin"
$outputFile = "wholefile.bin"
# For each url, download in a temp file, then append to output file
foreach ($myUrl in $arrUrls)
{
Invoke-WebRequest -Uri $myUrl -OutFile $tempFile
$byteArray = Get-Content $tempFile -AsByteStream -Raw
Add-Content $outputFile -Value $byteArray -AsByteStream
}
I've written (partly by myself) a script which get the ip address with a webrequest and save it as variable for a dyndns update. If I start the script via ISE or directly with PowerShell it works as expected. But if I start it via scheduled task the web request, whether with invoke-webrequest or curl doesn't works. The script will be started but the variable is empty. If I try to save the output of the command to a file its also empty, so I don't know why it doesn't work.
Here is the script:
# Get IPv4 and IPv6 from wtfismyip.com
$strIPv6 = Invoke-WebRequest -uri http://[2a01:4f9:4b:4c8f::2]/text | select Content -ExpandProperty Content
$strIPv4 = Invoke-WebRequest -uri http://95.217.228.176/text | select Content -ExpandProperty Content
$strIPs = $strIPv4 + "," + $strIPv6
$strIPs = [string]::join("",($strIPs.Split("`n")))
echo "IPs" >> C:\temp\test.txt
echo $strIPs >> C:\temp\test.txt
$strDYNDNS_URL = "https://dyndns.strato.com/nic/update?hostname=sub.domain.de&myip=$strIPs"
# Debug Informations
write "IPv4: $strIPv4"
write "IPv6: $strIPv6"
write "Strato URL: $strDYNDNS_URL"
# Strato DynDNS Update
$user = "domainuser"
$pass = "pass"
$pair = "${user}:${pass}"
$bytes = [System.Text.Encoding]::ASCII.GetBytes($pair)
$base64 = [System.Convert]::ToBase64String($bytes)
$basicAuthValue = "Basic $base64"
$headers = #{ Authorization = $basicAuthValue }
Invoke-WebRequest -uri $strDYNDNS_URL -Headers $headers
The scheduled task runs for testing with an admin user with the following settings:
Start with highest privileges
Trigger: every hour
Cmd: powershell.exe ExecutionPolicy Bypass -Command "C:\Daten\Scripte\StratoDynDNS-Update.ps1"
Does have anyone an idea why it doesn't work? Or is there a better solution?
The goal is to make a dns update at strato because the ISP of my client doesn't provide static ips, even with a business internet connection.
is there a way to convert html to plaintext?
I have a script that exports all NuGet-Licenses which been used in a visual studio project to a textfile.
Unfortunately the exports are mostly in HTML, and I found no way to solve it.
# Run in Package Manager Console with `./download-packages-license.ps1`.
# If access denied, execute `Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned`.
# Save licenses to One text file and one csv file instead of individual files
$LicensesFile = (Join-Path (pwd) 'licenses\Licenses.txt')
$LicensesFile_csv = (Join-Path (pwd) 'licenses\Licenses.csv')
$results = #()
# Below 2 lines to comment if you uncomment Split-Path ..
$solutionFile = "d:\Solutions\SolFile.sln"
cd "d:\Solutions"
# Uncomment below line if you wish to want to use above 2 lines
# Split-Path -parent $dte.Solution.FileName | cd;
New-Item -ItemType Directory -Force -Path ".\licenses";
#( Get-Project -All | ? { $_.ProjectName } | % {
Get-Package -ProjectName $_.ProjectName | ? { $_.LicenseUrl }
} ) | Sort-Object Id -Unique | % {
$pkg = $_;
Try
{
if ($pkg.Id -notlike 'microsoft*' -and $pkg.LicenseUrl.StartsWith('http'))
{
Write-Host ("Download license for package " + $pkg.Id + " from " + $pkg.LicenseUrl);
#Write-Host (ConvertTo-Json ($pkg));
$licenseUrl = $pkg.LicenseUrl
if ($licenseUrl.contains('github.com')) {
$licenseUrl = $licenseUrl.replace("/blob/", "/raw/")
}
$extension = ".txt"
if ($licenseUrl.EndsWith(".md"))
{
$extension = ".md"
}
(New-Object System.Net.WebClient).DownloadFile($licenseUrl, (Join-Path (pwd) 'licenses\') + $pkg.Id + $extension);
$licenseText = get-content "$((Join-Path (pwd) 'licenses\') + $pkg.Id + $extension)"
Remove-Item $((Join-Path (pwd) 'licenses\') + $pkg.Id + $extension) -ErrorAction SilentlyContinue -Force
$data = '' | select PkgId, LicenseText
$data.PkgId = $pkg.Id
$data.LicenseText = $licenseText | Out-String
$results += $data
# save in txt file
"Designation: NugetPackage $($pkg.Id)" | Add-Content $LicensesFile
$licenseText | Add-Content $LicensesFile
"" | Add-Content $LicensesFile
"" | Add-Content $LicensesFile
"" | Add-Content $LicensesFile
"" | Add-Content $LicensesFile
Write-Host "Package $($pkg.Id): License Text saved to $LicensesFile" -ForegroundColor Green
}
}
Catch [system.exception]
{
Write-Host ("Could not download license for " + $pkg.Id)
}
}
# save in .csv file
$results | Export-Csv $LicensesFile_csv -nti
Source of the Script here
A user also said ,,Unfortunately, most license URLs now point to HTML-only versions (early 2020). For example, licenses.nuget.org ignores any "Accept: text/plain" (or json) headers and returns html regardless"
So is there even a way to get the license information in plaintext?
Thanks and stay healthy!
So is there even a way to get the license information in plaintext?
Actually, we do not recommend that you convert the html file into plaintext format. And when you get the license data from nuget.org, it is the data returned from the site in full HTML format, which is designed by that.
The returned data also contains various formats for the license field, so we should not easily modify the accepted data format(such as plaintext ). And if possible, the only way to do this is to get rid of the HTML format fields from the source data, but however, it is impossible by Powershell and it cannot be done so far.
Therefore, in order to strictly follow the format of the returned data, it is best to use an HTML file to receive license info. It can maintain consistency with the website in the form of html.
Suggestion
1) change these in powershell:
$LicensesFile = (Join-Path (pwd) 'licenses\Licenses.html')
$LicensesFile_csv = (Join-Path (pwd) 'licenses\Licenses_csv.html')
And then you can get what you want.
Hope it could help you.
This question already has answers here:
How do I pass variables with the Invoke-Command cmdlet?
(3 answers)
Closed 6 years ago.
I'm currently writing a relatively easy PowerShell script to restart/stop/start a service on a remote machine. Everything is working well up until I decide to pass a $Service variable to -Name parameter in the Invoke-Command Scriptblock. I know I'm doing something wrong or forgetting something but your help would be greatly appreciated. Here is the code with the part giving me problems highlighted
[CmdletBinding()] Param([Parameter(Mandatory=$True,Position=1)]
[string]$Server,
[Parameter(Mandatory=$True)]
[string]$Service)
get-service -ComputerName $Server -Name "$Service"
Write-Host("------------------------------------------------")
Write-Host("Execute action on selected service: ")
Write-Host("1. Restart service ")
Write-Host("2. Stop service ")
Write-Host("3. Start service")
$choice = Read-Host -Prompt "Your choice"
switch ($choice)
{
1 {Invoke-command -Computername $Server {Restart-Service -Name "$Service" } }
2 {Invoke-command -ComputerName $Server {Stop-Service -Name "$Service" } }
3 {Invoke-command -ComputerName $Server {Start-Service -Name "$Service" } }
}
I have tried:
Single quotes around $Service
Double quotes around $Service
using param([String]$Service) in scriptblock
I keep getting the same error over and over:
Cannot bind argument to parameter 'Name' because it is an empty string.
+ CategoryInfo : InvalidData: (:) [Stop-Service], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorEmptyStringNotAllowed,Microsoft.PowerShell.Commands.StopServiceCommand
I know it has to do with the fact Im trying to run local variable on a remote machine. but could someone point me in the right direction , I would love for the script to simply use the mandatory parameters
Using the approach mentionned here How to pass local variable to Invoke-Command's I modified the code like so:
1 {Invoke-command -Computername $Server {param ([string] $srv = $Service) Restart-Service -Name "$srv" } }
Unfortunately the error persists
the syntax for parameter passing into a scriptblock should be as follows:
(Corrected as #pk198105 suggested)
Invoke-command -Computername $Server {
param($service)
Restart-Service -Name "$Service"
} -ArgumentList $service
both argumentlist and param are required
ok thanks in part to Abhijith pk
the correct way to implement this is as follows:
Invoke-command -Computername $Server {param ($Service) Restart-Service -Name "$service" } -ArgumentList $service
Thank you