BrazenCloud.ADK.psm1
<#
Code in this file will be added to the beginning of the .psm1. For example, you should place any using statements here. #> # suppressing the warning from the scriptblock [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')] param() Function Get-BcAgentInstallPath { [OutputType([System.IO.DirectoryInfo], ParameterSetName = 'dirInfo')] [OutputType([System.String], ParameterSetName = 'str')] [cmdletbinding( DefaultParameterSetName = 'dirInfo' )] param ( [Parameter( ParameterSetName = 'str' )] [switch]$AsString ) if ($AsString.IsPresent) { Get-ChildItem 'C:\Program Files\Runway' | ForEach-Object { $_.FullName } } else { Get-ChildItem 'C:\Program Files\Runway' } } Function Invoke-BcAction { [cmdletbinding( DefaultParameterSetName = 'folderAction', SupportsShouldProcess, ConfirmImpact = 'High' )] param ( [ValidateScript( { if (Test-Path $_ -PathType Leaf) { $_ -like '*manifest.txt' } elseif (Test-Path $_ -PathType Container) { Test-Path $_\manifest.txt -PathType Leaf } } )] [Parameter( Mandatory, ParameterSetName = 'folderAction', HelpMessage = 'Path must be a manifest.txt file or a folder containing one.' )] [string]$Path, [Parameter( Mandatory, ParameterSetName = 'folderAction' )] [string]$UtilityPath, [string]$WorkingDirectory, [hashtable]$Settings, [switch]$PreserveWorkingDirectory, [switch]$IgnoreRequiredParameters ) $ip = $InformationPreference $InformationPreference = 'Continue' $agentPath = Get-BcAgentInstallPath -AsString | Select-Object -First 1 # If the path is a folder, append manifest.txt if (Test-Path $Path -PathType Container) { $sPath = "$path\settings.json" $Path = "$((Resolve-Path $Path).Path)\manifest.txt" } else { $sPath = "$(Split-Path $path)\settings.json" } $splat = @{ AgentPath = $agentPath Parameters = $Settings } Join-BcSettingsHashtable @splat | ConvertTo-Json | Out-File $sPath # If no working dir is passed, use something in TEMP $actionRun = "Action_$(Get-Date -UFormat %s)" if ($PSBoundParameters.Key -notcontains 'WorkingDirectory') { $WorkingDir = "$($env:TEMP)\$actionRun" } if (Test-Path $WorkingDir) { Write-Verbose 'The working directory already exists, clear it?' if ($PSCmdlet.ShouldProcess($WorkingDir, 'Remove-Item')) { Remove-Item $WorkingDir -Recurse -Force } else { $PSCmdlet.ShouldProcess return } } if (Test-Path "$($env:TEMP)\action.app") { Remove-Item "$($env:TEMP)\action.app" -Force } if (-not $IgnoreRequiredParameters.IsPresent) { $parametersPath = "$(Split-Path $Path)\parameters.json" if (Test-Path $parametersPath) { $params = Get-Content $parametersPath | ConvertFrom-Json $params | Where-Object { $_.PSObject.Properties.Name -contains 'IsOptional' } | Where-Object { $_.IsOptional.ToString() -eq 'false' } | ForEach-Object { if ($Settings.Keys -notcontains $_.Name) { Throw "Mandatory parameter: '$($_.Name)' was not provided. Pass a value via -Settings or use -IgnoreRequiredParameters" } } } } # Build Action $buildSplat = @{ Path = 'cmd.exe' ArgumentList = "/C .\runway.exe -N build -i $Path -o $($env:TEMP)\action.app" WorkingDirectory = (Split-Path $UtilityPath) WindowStyle = 'Hidden' PassThru = $true RedirectStandardError = "$($env:TEMP)\buildstderr_$actionRun.txt" RedirectStandardOutput = "$($env:TEMP)\buildstdout_$actionRun.txt" } Write-Verbose 'Building the action...' $actionProc = Start-Process @buildSplat -Wait $buildStdErr = Get-Content "$($env:TEMP)\buildstderr_$actionRun.txt" if ($buildStdErr.Length -gt 0) { Throw "Error in build: $buildStdErr" } # Remove settings.json Remove-Item $sPath -Force # Run Action $runSplat = @{ Path = 'cmd.exe' ArgumentList = "/C .\runner.exe run --action_zip $($env:TEMP)\action.app --path $WorkingDir" WorkingDirectory = $agentPath WindowStyle = 'Hidden' PassThru = $true RedirectStandardError = "$($env:TEMP)\runstderr_$actionRun.txt" RedirectStandardOutput = "$($env:TEMP)\runstdout_$actionRun.txt" } Write-Verbose 'Running the action...' $actionProc = Start-Process @runSplat # Stream std.out While (-not (Test-Path $WorkingDir\std.out)) { $runStdErr = Get-Content "$($env:TEMP)\runstderr_$actionRun.txt" if ($runStdErr.Length -gt 0) { Throw "Error in run: $runStdErr" } Start-Sleep -Seconds 1 } Write-Verbose 'The following output is stdout from executing the action:' $stream = [System.IO.File]::Open("$WorkingDir\std.out", [System.IO.FileMode]::OpenOrCreate, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite) $reader = [System.IO.StreamReader]::new($stream) $stdOut = & { while ($null -ne ($line = $reader.ReadLine())) { $line Write-Information $line } while (-not $actionProc.HasExited) { while ($null -ne ($line = $reader.ReadLine())) { $line Write-Information $line } Start-Sleep -Seconds 1 } } $reader.Close() # Collect results $resultPath = "$($env:TEMP)\Results_$actionRun.zip" if (Test-Path $resultPath) { Write-Verbose 'The results file already exists, overwrite?' if ($PSCmdlet.ShouldProcess($resultPath, 'Remove-Item')) { Remove-Item $resultPath -Recurse -Force } } if ((Get-ChildItem $WorkingDir\results).Count -gt 0) { Compress-Archive "$WorkingDir\results" -DestinationPath $resultPath } else { Write-Warning 'No results to be collected.' } $out = [pscustomobject]@{ Build = @{ StdOut = Get-Content "$($env:TEMP)\buildstdout_$actionRun.txt" StdErr = Get-Content "$($env:TEMP)\buildstderr_$actionRun.txt" } Run = @{ StdOut = Get-Content "$($env:TEMP)\runstdout_$actionRun.txt" StdErr = Get-Content "$($env:TEMP)\runstderr_$actionRun.txt" } Results = if (Test-Path $resultPath) { Get-Item $resultPath } else { $null } StdOut = $stdOut } # Clean up redirects @("buildstdout_*.txt", "buildstderr_*.txt", "runstdout_*.txt", "runstderr_*.txt") | ForEach-Object { Remove-Item "$($env:TEMP)\$_" -ErrorAction SilentlyContinue -Force } # Clean up workingDir if (-not ($PreserveWorkingDirectory.IsPresent)) { Remove-Item $WorkingDir -Recurse -Force } else { $out | Add-Member -MemberType NoteProperty -Name 'WorkingDirectory' -Value (Get-Item $WorkingDir) } $out $InformationPreference = $ip } Function Join-BcSettingsHashtable { [cmdletbinding()] param ( [string]$AgentPath, [hashtable]$Parameters ) $runnerSettings = Get-Content $AgentPath\runner_settings.json | ConvertFrom-Json $settings = [ordered]@{ runner_identity = $runnerSettings.identity host = $runnerSettings.host thread_id = '' job_id = '' action_instance_id = '' repository_action_id = '' prodigal_object_id = '' prodigal_asset_name = $env:COMPUTERNAME atoken = $runnerSettings.atoken } $x = 0 foreach ($key in $Parameters.Keys) { $settings.Insert($x, $key, $Parameters[$key]) $x++ } $settings } Function New-BcAction { [cmdletbinding(SupportsShouldProcess)] param ( [string]$TemplateName = 'PowerShellAction', [Parameter( Mandatory, HelpMessage = 'Parent path of the action.' )] [string]$Destination, [switch]$Force ) if (-not (Get-Module Plaster -ListAvailable)) { if ($Force.IsPresent) { Install-Module Plaster -Repository PsGallery -Force } else { Write-Warning 'This command requires the Plaster module. Please install or use the -Force switch to automatically isntall.' return } } Invoke-Plaster -TemplatePath $PSScriptRoot\templates\$TemplateName -DestinationPath $Destination } <# Code in this file will be added to the end of the .psm1. For example, you should set variables or other environment settings here. #> # Argument completer for New-BcAction $scriptBlock = { param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) Get-ChildItem $PSScriptRoot\templates\$wordToComplete* | Select-Object -ExpandProperty Name } Register-ArgumentCompleter -CommandName New-BcAction -ParameterName TemplateName -ScriptBlock $scriptBlock |