JBUtils.psm1
|
#Region '.\Private\Get-UsernameSID.ps1' -1 <# .SYNOPSIS Create a function to retrieve the SID for a user account on a machine. #> function Get-UsernameSID { param ( [String]$AccountName ) $ntUserObject = New-Object System.Security.Principal.NTAccount($AccountName) $ntUserSid = $ntUserObject.Translate([System.Security.Principal.SecurityIdentifier]) $ntUserSid.Value } #EndRegion '.\Private\Get-UsernameSID.ps1' 14 #Region '.\Private\Invoke-Timeout.ps1' -1 <# .SYNOPSIS Wrapper for timeout.exe to support Pester mocking. .DESCRIPTION Wrapper for timeout.exe to support Pester mocking. .EXAMPLE Invoke-Timeout /t 30 .NOTES N/A #> function Invoke-Timeout { param ( [Parameter(Mandatory = $true)] [ValidateSet('/t')] [String]$TimeSwitch, [Parameter(Mandatory = $true)] [Int]$Seconds, [ValidateSet('/nobreak')] [String]$NoBreakSwitch ) $timeout = Get-Command -Name "$env:windir/System32/timeout.exe" -ErrorAction Stop $ArgumentList = ($TimeSwitch, $Seconds) if ($NoBreakSwitch) { $ArgumentList += $NoBreakSwitch } $stderrOutFile = New-TemporaryFile try { Start-Process ` -FilePath $timeout.Source ` -ArgumentList $ArgumentList ` -NoNewWindow ` -Wait ` -ErrorAction Stop } catch { Write-Verbose -Message $_.Exception.Message Start-Process ` -FilePath $timeout.Source ` -ArgumentList $ArgumentList ` -Wait ` -RedirectStandardError $stderrOutFile.FullName } $stdErr = Get-Content -Path $stderrOutFile.FullName -Raw $stderrOutFile | Remove-Item -Force -ErrorAction SilentlyContinue if ($null -ne $stdErr) { throw $stdErr } } #EndRegion '.\Private\Invoke-Timeout.ps1' 57 #Region '.\Public\Add-AzPipelinesPathEntry.ps1' -1 <# .SYNOPSIS Prepend a value to the PATH environment variable in Azure Pipelines that can be used in subsequent tasks. .DESCRIPTION Prepend a value to the PATH environment variable in Azure Pipelines that can be used in subsequent tasks. It will only print the console syntax if running on a pipeline agent. .PARAMETER Path Path to prepend. .EXAMPLE Add-AzPipelinesPathEntry -Path C:/path/to/prepend .NOTES N/A #> function Add-AzPipelinesPathEntry { [CmdletBinding()] param ( [String[]]$Path ) if ($env:AGENT_JOBNAME) { $processedPaths = @( foreach ($entry in $Path) { $entry.Split([System.IO.Path]::PathSeparator) | Where-Object -FilterScript { $_ } } ) $resolvedPaths = @() $resolvedPaths += foreach ($entry in $processedPaths) { try { ( Resolve-Path -Path $entry -ErrorAction Stop ).Path } catch { Write-Warning -Message $_.Exception.Message $entry } } $joinedPaths = $resolvedPaths -join [System.IO.Path]::PathSeparator Write-Host -Object "##vso[task.prependpath]$joinedPaths" } } #EndRegion '.\Public\Add-AzPipelinesPathEntry.ps1' 48 #Region '.\Public\ConvertFrom-EncryptedSecureString.ps1' -1 <# .SYNOPSIS Converts a SecureString to plain text in one step. .DESCRIPTION Converts a SecureString to plain text in one step. .PARAMETER SecureString SecureString to convert. .EXAMPLE $secureStringVar | ConvertFrom-EncryptedSecureString .LINK https://stackoverflow.com/a/28353003/14628263 .NOTES N/A #> function ConvertFrom-EncryptedSecureString { [CmdletBinding()] [OutputType([String])] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [SecureString]$SecureString ) process { $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureString) [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR) [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($BSTR) } } #EndRegion '.\Public\ConvertFrom-EncryptedSecureString.ps1' 34 #Region '.\Public\ConvertTo-Psd1.ps1' -1 <# .SYNOPSIS Serializes an object to PowerShell data-file (.psd1) literal syntax. .DESCRIPTION Recursively renders strings, numbers, booleans, arrays, and dictionaries as PowerShell data-file literal text, suitable for writing a hand-editable .psd1 file. Unsupported types are rendered as their quoted string representation. .PARAMETER InputObject The object to serialize. Accepts pipeline input. .PARAMETER Indent The current indentation level, in 4-space increments. Used internally for recursive calls; callers normally omit it. .OUTPUTS System.String .EXAMPLE ConvertTo-Psd1 -InputObject @{ Name = 'MyModule'; Tags = @('PSEdition_Core') } .NOTES N/A #> function ConvertTo-Psd1 { [CmdletBinding()] [OutputType([String])] param ( [Parameter(Mandatory, ValueFromPipeline)] [AllowNull()] $InputObject, [Int]$Indent = 0 ) process { $indentText = ' ' * ($Indent * 4) $childPad = ' ' * (($Indent + 1) * 4) if ($null -eq $InputObject) { '$null' } elseif ($InputObject -is [Bool]) { if ($InputObject) { '$true' } else { '$false' } } elseif ($InputObject -is [Int] -or $InputObject -is [Long] -or $InputObject -is [Double] -or $InputObject -is [Decimal]) { "$InputObject" } elseif ($InputObject -is [System.Collections.IDictionary]) { if ($InputObject.Count -eq 0) { '@{}' } else { $keyWidth = ($InputObject.Keys | ForEach-Object -Process { "$_".Length } | Measure-Object -Maximum).Maximum $lines = [System.Collections.Generic.List[String]]::new() $lines.Add('@{') foreach ($key in $InputObject.Keys) { # The recursive call already carries absolute indentation, so the opening # token goes inline after "key =" and continuation lines are added as-is. $rendered = ConvertTo-Psd1 -InputObject $InputObject[$key] -Indent ($Indent + 1) $lines.Add("$childPad$("$key".PadRight($keyWidth)) = $rendered") } $lines.Add("$indentText}") $lines -join "`n" } } elseif ($InputObject -is [System.Collections.IEnumerable] -and $InputObject -isnot [String]) { $items = @($InputObject) if ($items.Count -eq 0) { '@()' } else { $lines = [System.Collections.Generic.List[String]]::new() $lines.Add('@(') foreach ($item in $items) { $lines.Add("$childPad$(ConvertTo-Psd1 -InputObject $item -Indent ($Indent + 1))") } $lines.Add("$indentText)") $lines -join "`n" } } else { "'" + ("$InputObject" -replace "'", "''") + "'" } } } #EndRegion '.\Public\ConvertTo-Psd1.ps1' 95 #Region '.\Public\Enable-Tls12.ps1' -1 <# .SYNOPSIS Changes the security protocol of the current session to 1.2. .DESCRIPTION Changes the security protocol of the current session to 1.2. .EXAMPLE Enable-Tls12 Enables TLS 1.2 for the current session. .EXAMPLE Enable-Tls12 -Persist Enables TLS 1.2 machine-wide. .LINK https://docs.microsoft.com/en-us/troubleshoot/azure/active-directory/enable-support-tls-environment .NOTES N/A #> function Enable-Tls12 { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingCmdletAliases', '')] [CmdletBinding()] param ( [Switch]$Persist ) Write-Verbose -Message '[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12' [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 if ($Persist) { $null = Test-PSEnvironment -CheckAdmin -Exit $psRegPaths = ( 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Client', 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Server' ) foreach ($regPath in $psRegPaths) { $progress = @{ Activity = 'Modifying Registry' Status = "Key: $regPath" } Write-Progress @progress $null = New-Item -Path $regPath -Force $null = New-ItemProperty -Path $regPath -Name DisabledByDefault -Value 0 -PropertyType DWord -Force $null = New-ItemProperty -Path $regPath -Name Enabled -Value 1 -PropertyType DWord -Force } # Use reg instead of New-ItemProperty because it's not clear how # to modify both the 32 and 64 bit registries via PowerShell $startProcess = @{ FilePath = 'reg' NoNewWindow = $true Wait = $true } $regPaths = ( 'HKLM\SOFTWARE\Microsoft\.NETFramework\v2.0.50727', 'HKLM\SOFTWARE\Microsoft\.NETFramework\v4.0.30319', 'HKLM\SOFTWARE\Wow6432Node\Microsoft\.NETFramework\v2.0.50727', 'HKLM\SOFTWARE\Wow6432Node\Microsoft\.NetFramework\v4.0.30319' ) $regValues = ( 'SystemDefaultTlsVersions', 'SchUseStrongCrypto' ) $enable = ( '/t', 'REG_DWORD', '/d', '1' ) $registries = ( '/reg:32', '/reg:64' ) $totalCommands = $regPaths.Count * $regValues.Count * $registries.Count $count = 0 foreach ($regPath in $regPaths) { foreach ($regValue in $regValues) { foreach ($registry in $registries) { $count++ $startProcess['ArgumentList'] = ( 'add', $regPath, '/f' ) # Start-Process @startProcess $startProcess['ArgumentList'] += ( '/v', $regValue, $registry ) $startProcess['ArgumentList'] += $enable $message = 'reg ' + ($startProcess['ArgumentList']) -join ' ' Write-Verbose -Message $message $progress = @{ Activity = 'Modifying Registry' Status = "Key: $regPath, SubKey: $regValue, Value: 1" CurrentOperation = "$count/$totalCommands" PercentComplete = (($count / $totalCommands) * 100) } Write-Progress @progress Start-Process @startProcess -RedirectStandardOutput "$env:TEMP/stdout.log" } } } Write-Progress @progress -Completed } } #EndRegion '.\Public\Enable-Tls12.ps1' 103 #Region '.\Public\Export-Screenshot.ps1' -1 <# .SYNOPSIS Exports a screenshot as a bitmap. .DESCRIPTION Exports a screenshot as a bitmap. .PARAMETER OutFile Path to save the screenshot to. .EXAMPLE Export-Screenshot -OutFile $env:USERPROFILE/Downloads/Screenshot.bmp .NOTES N/A .LINK https://www.pdq.com/blog/capturing-screenshots-with-powershell-and-net/ #> function Export-Screenshot { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute( 'PSUseCompatibleTypes', '', Justification = 'System.Windows.Forms is loaded explicitly before the Windows-only type is used.' )] [CmdletBinding()] param ( [String]$OutFile = "$env:TEMP/$( New-Guid ).bmp" ) begin { Add-Type -AssemblyName System.Windows.Forms Add-Type -AssemblyName System.Drawing $screen = [System.Windows.Forms.SystemInformation]::VirtualScreen $Script:width = $screen.Width $Script:height = $screen.Height $Script:left = $screen.Left $Script:top = $screen.Top } process { $bitmap = New-Object -TypeName System.Drawing.Bitmap -ArgumentList ($Script:width, $Script:height) $graphic = [System.Drawing.Graphics]::FromImage($bitmap) $graphic.CopyFromScreen($Script:left, $Script:top, 0, 0, $bitmap.Size) $bitmap.Save($OutFile) Get-Item -Path $OutFile } } #EndRegion '.\Public\Export-Screenshot.ps1' 50 #Region '.\Public\Get-EnvironmentVariable.ps1' -1 <# .SYNOPSIS Gets the value of of an environment variable. .DESCRIPTION Gets the value of of an environment variable. .PARAMETER Name Name of the environment variable. .PARAMETER Scope Environment scope: Machine, Process, or User. .EXAMPLE Get-EnvironmentVariable -Name PATH -Scope User .NOTES N/A #> function Get-EnvironmentVariable { [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String[]]$Name, [ValidateSet('Machine', 'Process', 'User')] [String]$Scope = 'Process' ) process { foreach ($item in $Name) { [PSCustomObject]@{ Name = $item Value = [System.Environment]::GetEnvironmentVariable($item, $Scope) Scope = $Scope } } } } #EndRegion '.\Public\Get-EnvironmentVariable.ps1' 40 #Region '.\Public\Get-PatPSCredential.ps1' -1 <# .SYNOPSIS Creates a PSCredential object for a Personal Access Token. .DESCRIPTION Creates a PSCredential object for a Personal Access Token. .PARAMETER Pat The Personal Access Token (PAT) to use. .PARAMETER Username The username to associate with the PAT. .EXAMPLE An example .NOTES General notes Parameter description .PARAMETER Username Parameter description .EXAMPLE Get-PatPSCredential -Pat 'my-personal-access-token' -Username 'my-username' .NOTES N/A #> function Get-PatPSCredential { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] [CmdletBinding()] param ( [Parameter()] [String]$Pat = $env:SYSTEM_ACCESSTOKEN, [String]$Username = 'PAT' ) if ([string]::IsNullOrEmpty($Pat)) { throw 'No PAT provided and SYSTEM_ACCESSTOKEN environment variable is not set' } $securePat = ConvertTo-SecureString -String $Pat -AsPlainText -Force New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $Username, $securePat } #EndRegion '.\Public\Get-PatPSCredential.ps1' 47 #Region '.\Public\Get-PSVersion.ps1' -1 <# .SYNOPSIS A simple function wrapper for getting the value of $PSVersionTable.PSVersion. .DESCRIPTION A simple function wrapper for getting the value of $PSVersionTable.PSVersion, used for unit testing. .EXAMPLE Get-PSVersion .NOTES N/A #> function Get-PSVersion { [CmdletBinding()] param() $PSVersionTable.PSVersion } #EndRegion '.\Public\Get-PSVersion.ps1' 20 #Region '.\Public\Get-TempDirectory.ps1' -1 <# .SYNOPSIS Gets a temporary directory path for the current host. .DESCRIPTION Uses the runtime temp-path resolver because $env:TEMP is not set reliably on macOS or Linux. .OUTPUTS System.String .EXAMPLE Get-TempDirectory .NOTES N/A #> function Get-TempDirectory { [CmdletBinding()] [OutputType([String])] param() [System.IO.Path]::GetTempPath() } #EndRegion '.\Public\Get-TempDirectory.ps1' 24 #Region '.\Public\Initialize-GitConfig.ps1' -1 <# .SYNOPSIS Sets the git user name and email in a given scope. .DESCRIPTION Sets the git user name and email in a given scope. .PARAMETER Path Path of the repo. .PARAMETER UserName User name to add to the git config. .PARAMETER UserEmail Email to add to the git config. .PARAMETER Scope Scope of the git config. .EXAMPLE Initialize-GitConfig -UserName "Git User" -UserEmail email@example.com .NOTES N/A #> function Initialize-GitConfig { [CmdletBinding()] param ( [Parameter(ValueFromPipelineByPropertyName = $true)] [Alias('FullName', 'LiteralPath')] [String[]]$Path = $PWD, [String]$UserName = $env:BUILD_REQUESTEDFOR, [String]$UserEmail = $env:BUILD_REQUESTEDFOREMAIL, [ValidateSet('System', 'Global', 'WorkTree', 'Local')] [String]$Scope = 'Local' ) begin { $script:CurrentLocation = $PWD $script:Git = @{ FilePath = Get-Command -Name git.exe | Select-Object -ExpandProperty Source NoNewWindow = $true Wait = $true } $script:Config = @( 'config', "--$($Scope.ToLower())" ) } process { Start-Process @script:Git -ArgumentList ($script:Config + ('http.version', 'HTTP/1.1')) $userEntries = @( @{ ArgumentList = $script:Config + ( '--replace-all', 'user.email', "`"$UserEmail`"" ) }, @{ ArgumentList = $script:Config + ( '--replace-all', 'user.name', "`"$UserName`"" ) } ) foreach ($location in $Path) { foreach ($user in $userEntries) { Start-Process @script:Git @user -WorkingDirectory $location } } } end { Set-Location -Path $script:CurrentLocation } } #EndRegion '.\Public\Initialize-GitConfig.ps1' 81 #Region '.\Public\Install-NugetCLI.ps1' -1 <# .SYNOPSIS Installs NuGet CLI. .DESCRIPTION Installs NuGet CLI and adds it to the path. .PARAMETER Path Path to download and execute nuget.exe from. .EXAMPLE Install-NugetCli .NOTES N/A #> function Install-NugetCli { [CmdletBinding()] param ( [String]$Path = ( Join-Path -Path $env:APPDATA -ChildPath 'NuGet' ) ) $null = New-Item -Path $Path -ItemType Directory -Force -ErrorAction SilentlyContinue $currentProgressPreference = $ProgressPreference $ProgressPreference = 'SilentlyContinue' Write-Host -Object "Downloading nuget.exe to $Path..." Invoke-WebRequest ` -Uri 'https://dist.nuget.org/win-x86-commandline/latest/nuget.exe' ` -UseBasicParsing ` -OutFile "$Path/nuget.exe" $ProgressPreference = $currentProgressPreference Write-Host -Object "Adding $Path to %PATH%..." $null = Set-EnvironmentVariable -Name 'PATH' -Value $Path -Scope User -Append 3>&1 $env:PATH = $Path + [System.IO.Path]::PathSeparator + $env:PATH Remove-Item -Path 'Alias:nuget' -ErrorAction SilentlyContinue Set-Alias -Name 'nuget' -Value ( Join-Path -Path $Path -ChildPath 'nuget.exe' ) Get-Command -Name 'nuget.exe' } #EndRegion '.\Public\Install-NugetCLI.ps1' 39 #Region '.\Public\Reset-ConsoleColor.ps1' -1 <# .SYNOPSIS Resets the current console to the default colors. .DESCRIPTION Resets the current console to the default colors. .EXAMPLE Reset-ConsoleColor .NOTES N/A #> function Reset-ConsoleColor { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute( 'PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Resetting colors affects only the current console presentation.' )] [CmdletBinding()] param () [Console]::ResetColor() } #EndRegion '.\Public\Reset-ConsoleColor.ps1' 25 #Region '.\Public\Set-EnvironmentVariable.ps1' -1 <# .SYNOPSIS Sets an environment variable. .DESCRIPTION Sets an environment variable. .PARAMETER Name Name of the environment variable. .PARAMETER Value Parameter description .PARAMETER Scope Environment scope: Machine, Process, or User. .PARAMETER Append Appends the given value to the variable instead of replacing it. .PARAMETER Delete Deletes the named environment variable. .PARAMETER PassThru Outputs an environment variable object to the pipeline. .PARAMETER Force Sets the value even if it already exists in the environment variable, and doesn't prompt to modify the Path. .EXAMPLE Set-EnvironmentVariable -Name PATH -Value 'C:/Program Files/NuGet' -Scope Machine -Append Adds 'C:/Program Files/NuGet' to the System PATH variable. .NOTES N/A #> function Set-EnvironmentVariable { [CmdletBinding(SupportsShouldProcess = $true, DefaultParameterSetName = 'Set')] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [String]$Name, [Parameter(ParameterSetName = 'Set', ValueFromPipelineByPropertyName = $true)] [String]$Value, [ValidateSet('Machine', 'Process', 'User')] [String]$Scope = 'Process', [Parameter(ParameterSetName = 'Set')] [Switch]$Append, [Parameter(ParameterSetName = 'Delete')] [Switch]$Delete, [Parameter(ParameterSetName = 'Set')] [Switch]$PassThru, [Parameter(ParameterSetName = 'Set')] [Switch]$Force ) begin { if ($Scope -eq 'Machine') { $null = Test-PSEnvironment -CheckAdmin -Exit } } process { if ($Delete) { if ($PSCmdlet.ShouldProcess("$($Scope):$Name", 'Delete') -or $Force) { [System.Environment]::SetEnvironmentVariable($Name, $null, $Scope) Set-Item -Path "env:$Name" -Value $null -Force } return } $envVariableValue = Get-EnvironmentVariable -Name $Name -Scope $Scope | Select-Object -ExpandProperty Value if ($null -eq $envVariableValue) { Write-Verbose -Message "Environment variable $($Scope):$Name does not exist." $envVariableValue = '' } $isNotArray = $envVariableValue -notmatch [System.IO.Path]::PathSeparator $isNotEqualToEnvValue = $envVariableValue.Trim() -ne $Value.Trim() $isNotArrayIsNotEqual = $isNotArray -and $isNotEqualToEnvValue $isArray = $envVariableValue -match [System.IO.Path]::PathSeparator $isNotValueInEnvArray = $envVariableValue -notmatch [Regex]::Escape($Value) $isArrayIsNotMatching = $isArray -and $isNotValueInEnvArray if ($Force -or $isNotArrayIsNotEqual -or $isArrayIsNotMatching) { if ($Name -eq 'PATH' -and (-not $Append) -and (-not $Force)) { Write-Warning -Message ( "This will overwrite all entries in the $($Scope):PATH variable with: $Value" ) $shouldAppend = Read-Host -Prompt "Should $Value be appended instead? (y/n)" if ($shouldAppend -eq 'y') { $Append = $true } } # Ensure that the existing PATH variable doesn't get corrupted when appending $valueWithPathSeparator = if ( $Append -and $Name -eq 'PATH' -and $Value[0] -ne [System.IO.Path]::PathSeparator ) { [System.IO.Path]::PathSeparator + $Value } else { $Value } $finalValue = if ($Append) { $envVariableValue + $valueWithPathSeparator } else { $valueWithPathSeparator } if ($PSCmdlet.ShouldProcess("$($Scope):$Name", "Setting value to $finalValue") -or $Force) { [System.Environment]::SetEnvironmentVariable($Name, $finalValue, $Scope) if ($Name -match 'path' -and $isArray) { $scopedValue = ( Get-Item -Path "env:$($Name.ToUpper())" ).Value.Split([System.IO.Path]::PathSeparator) $newValue = $finalValue.Split([System.IO.Path]::PathSeparator) if ( Compare-Object ` -ReferenceObject ( $scopedValue | Sort-Object ) ` -DifferenceObject ( $newValue | Sort-Object ) ) { $combinedValue = $scopedValue + $newValue $finalValue = $combinedValue -join [System.IO.Path]::PathSeparator } } # Only update process environment if we're targeting Process scope or PATH if ($Scope -eq 'Process' -or $Name -match 'path') { Set-Item -Path "env:$Name" -Value $finalValue -Force } } } else { Write-Warning -Message ( "The environment variable $($Scope):$Name already contains a value of $Value. " + 'Nothing was modified; use -Force to overwrite it.' ) } if ($PassThru) { Get-EnvironmentVariable -Name $Name -Scope $Scope } } } #EndRegion '.\Public\Set-EnvironmentVariable.ps1' 155 #Region '.\Public\Set-JsonFile.ps1' -1 <# .SYNOPSIS Writes an object to a JSON file. .DESCRIPTION Serializes the input object to JSON (depth 100, to cover deeply nested config/manifest objects) and writes it to the given path. .PARAMETER InputObject The object to serialize. Accepts pipeline input. .PARAMETER Path The file path to write to. .EXAMPLE $manifest | Set-JsonFile -Path './release-manifest.json' .NOTES N/A #> function Set-JsonFile { [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory, ValueFromPipeline)] [Object]$InputObject, [Parameter(Mandatory)] [String]$Path ) process { if ($PSCmdlet.ShouldProcess($Path, 'Write JSON file')) { $InputObject | ConvertTo-Json -Depth 100 | Set-Content -LiteralPath $Path -Encoding utf8 } } } #EndRegion '.\Public\Set-JsonFile.ps1' 37 #Region '.\Public\Show-ConsoleColor.ps1' -1 <# .SYNOPSIS Shows available colors that can be used in the console. .DESCRIPTION Shows available colors that can be used in the console and examples of what they will look like. .EXAMPLE Show-ConsoleColor Lists available colors and shows what the color will look like in the current console. .NOTES N/A #> function Show-ConsoleColor { [CmdletBinding()] param() Write-Host -Object ('{0,-120}' -f ' ') -ForegroundColor Black -BackgroundColor White foreach ($heading in 'Color', 'Foreground', 'Background') { Write-Host -Object ('{0,-40}' -f $heading) -ForegroundColor Black -BackgroundColor White -NoNewline } Write-Host $colors = [enum]::GetValues([System.ConsoleColor]) foreach ($color in $colors) { $object = @{ Object = ('{0,-40}' -f $color) } Write-Host @object -NoNewline Write-Host @object -ForegroundColor $color -NoNewline Write-Host @object -ForegroundColor $colors[$colors.Count - $color - 1] -BackgroundColor $color } } #EndRegion '.\Public\Show-ConsoleColor.ps1' 33 #Region '.\Public\Start-CliProcess.ps1' -1 <# .SYNOPSIS Starts a process using the .net Process class instead of Start-Process. .DESCRIPTION Starts a process using the .net Process class instead of Start-Process. This allows console output to be captured as well as non-zero exit codes at the same time. .PARAMETER FilePath Path to the process to start. .PARAMETER ArgumentList Arguments for the process. .PARAMETER WorkingDirectory Directory to execute the process in. .PARAMETER PassThru Pass the output to the pipeline. .PARAMETER Title Title of the progress bar that displays while the CLI is running. .PARAMETER NoProgress Output the command details to the information stream instead of as a progress bar. .PARAMETER RedirectStandardError Redirects the standard error stream to the standard output stream. Exe's that use StdErr for info: git .EXAMPLE Start-CliProcess -FilePath 'cmd' -ArgumentList '/c "echo hello"' -PassThru .NOTES Replaces Start-Process and Invoke-Process. Notes on avoiding process deadlocks when updating the output logic: https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.process.standarderror#remarks #> function Start-CliProcess { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingCmdletAliases', '')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')] [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(ValueFromPipelineByPropertyName = $true, Position = 0)] [Alias('LiteralPath', 'FullName')] [String[]]$FilePath, [String[]]$ArgumentList, [Parameter(ValueFromPipelineByPropertyName = $true)] [Alias('Directory')] [String]$WorkingDirectory = $PWD, [Switch]$PassThru, [Alias('Activity')] [String]$Title = 'Start CLI Process', [Switch]$NoProgress, [Switch]$RedirectStandardError ) begin { $script:processes = @() $script:noProgress = if ($NoProgress -or $env:AGENT_JOBNAME) { $true } else { $false } } process { $procInfoArgs = if ($ArgumentList.Count -gt 1) { $ArgumentList | ForEach-Object -Process { $thisTrimmed = $_.Trim() if ($thisTrimmed -match ' ' -and $thisTrimmed -notmatch '"') { "`"$thisTrimmed`"" } else { $thisTrimmed } } } elseif ($ArgumentList.Count -eq 1) { $ArgumentList.Trim() } foreach ($file in $FilePath) { $fileInfo = ( Get-Command -Name $file ).Source | Get-Item $command = "`"$($fileInfo.FullName)`" $($ArgumentList -join ' ')" if (-not $PSCmdlet.ShouldProcess($command, "Start process in '$WorkingDirectory'")) { continue } $processInfo = New-Object -TypeName 'System.Diagnostics.ProcessStartInfo' $processInfo.FileName = $fileInfo.FullName $processInfo.Arguments = $procInfoArgs $processInfo.WorkingDirectory = $WorkingDirectory $processInfo.CreateNoWindow = $true $processInfo.UseShellExecute = $false $processInfo.RedirectStandardOutput = $true $processInfo.RedirectStandardError = $true $process = New-Object -TypeName 'System.Diagnostics.Process' $process.StartInfo = $processInfo $progress = @{ Activity = $Title Status = "Starting $(( Get-Command -Name $fileInfo.FullName ).Name) in $WorkingDirectory" CurrentOperation = $command } if (-not $script:noProgress) { Write-Progress @progress } else { Write-ProgressToHost @progress } $null = $process.Start() $procState = 'Running' do { if ($procState -eq 'LastRun') { $readMethod = 'ReadToEnd' $procState = 'Finished' } else { $readMethod = 'ReadLine' } while (-not $process.StandardOutput.EndOfStream) { $process.StandardOutput.$readMethod() | ForEach-Object -Process { if ($PassThru) { $_ } else { Write-Host -Object $_ } if (-not $script:noProgress) { Write-Progress @progress } } } $stdErr = @() while (-not $process.StandardError.EndOfStream) { $process.StandardError.$readMethod() | ForEach-Object -Process { if ($_) { $stdErr += $_ } if ($PassThru) { $_ } elseif ($RedirectStandardError) { Write-Host -Object $_ } if (-not $script:noProgress) { Write-Progress @progress } } } if ($stdErr) { $stdErrString = $stdErr -join "`n" if (-not $RedirectStandardError) { Write-Error ` -Message $stdErrString ` -Category FromStdErr ` -TargetObject $fileInfo.Name if (-not $script:noProgress) { Write-Progress @progress } } } if ($process.HasExited -and $procState -eq 'Running') { $procState = 'LastRun' } } while ($procState -ne 'Finished') if (-not $script:noProgress) { Write-Progress @progress -Completed } if ( ($process.ExitCode -ne 0) -and ($ErrorActionPreference -ne 'SilentlyContinue') -and ($ErrorActionPreference -ne 'Ignore') ) { throw $process.ExitCode } $script:processes += $process.Id } } end { if ($script:processes) { Stop-Process -Id $script:processes -Force -PassThru -ErrorAction SilentlyContinue | Wait-Process } } } #EndRegion '.\Public\Start-CliProcess.ps1' 195 #Region '.\Public\Start-StopWatch.ps1' -1 <# .SYNOPSIS Starts a System.Diagnostics.Stopwatch instance. .DESCRIPTION Starts a System.Diagnostics.Stopwatch instance. .PARAMETER InputObject An existing Stopwatch object to start. .EXAMPLE $sw = Start-Stopwatch .NOTES N/A #> function Start-Stopwatch { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute( 'PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Starting an in-memory stopwatch does not change external system state.' )] [OutputType([System.Diagnostics.Stopwatch])] [CmdletBinding()] param ( [Parameter(ValueFromPipeline = $true)] [System.Object]$InputObject ) process { if ($InputObject) { $InputObject.Start() $InputObject } else { [System.Diagnostics.Stopwatch]::StartNew() } } } #EndRegion '.\Public\Start-StopWatch.ps1' 40 #Region '.\Public\Start-Timeout.ps1' -1 <# .SYNOPSIS Waits for a specified time with the option to press a key to continue. .DESCRIPTION Waits for a specified time with the option to press a key to continue. .PARAMETER Seconds The amount of seconds to wait before continuing. .PARAMETER NoBreak Removes the option to press a key to continue. .EXAMPLE Start-Timeout -Seconds 5 .NOTES Is just a more PS friendly wrapper for timeout.exe #> function Start-Timeout { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute( 'PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Waiting does not change external system state.' )] [CmdletBinding()] param ( [Int]$Seconds = 0, [Switch]$NoBreak ) if (Test-IsNonInteractiveShell) { for ($i = $Seconds; $i -ge 0; $i--) { $activity = "Waiting for $i seconds," Write-Progress -Activity $activity -Status 'press CTRL+C to quit ...' Start-Sleep -Seconds 1 } Write-Progress $activity -Completed } else { $scriptString = "Invoke-Timeout /t $Seconds" if ($NoBreak) { $scriptString += ' /nobreak' } $timeout = [ScriptBlock]::Create($scriptString) Invoke-Command -ScriptBlock $timeout } } #EndRegion '.\Public\Start-Timeout.ps1' 50 #Region '.\Public\Stop-DevProcess.ps1' -1 <# .SYNOPSIS Stops any processes that may interfere with a product build. .DESCRIPTION Stops any processes that may interfere with a product build, includes Visual Studio and Selenium ChromeDriver by default. .PARAMETER Optional A list of process names that will prompt to stop before stopping them. Always includes Visual Studio. .PARAMETER Required A list of process names that will be stopped without prompting. Always includes Selenium ChromeDriver. .EXAMPLE Stop-DevProcess .EXAMPLE Stop-DevProcess -Required @('*chromedriver', 'foviawebsdk') #> function Stop-DevProcess { [CmdletBinding( SupportsShouldProcess = $true, ConfirmImpact = 'Low' )] param ( [Parameter(Position = 0)] [string[]]$Optional, [string[]]$Required ) # $Optional += @( # 'devenv' # ) $Required += @( '*chromedriver' ) foreach ($processName in $Optional) { $processes = @( Get-Process -Name $processName -ErrorAction SilentlyContinue ) foreach ($process in $processes) { $programName = if ($process.Product) { $process.Product } else { $process.ProcessName } $nameString = "$programName [$($process.Id)]" $stop = Read-Host -Prompt ( "$nameString is running and could cause issues. Would you like to stop it? (y/n):" ) if ($stop.ToLower() -eq 'y') { if ($PSCmdlet.ShouldProcess($nameString, 'Stop Process')) { $null = Test-PSEnvironment -CheckAdmin -Exit $process | Stop-ProcessTree } } } } foreach ($processName in $Required) { $processes = @( Get-Process -Name $processName -ErrorAction SilentlyContinue ) foreach ($process in $processes) { $programName = if ($process.Product) { $process.Product } else { $process.ProcessName } $nameString = "$programName [$($process.Id)]" $stopMessage = "$nameString is running and will be stopped." Write-Host -Object $stopMessage if ($PSCmdlet.ShouldProcess($nameString, 'Stop Process')) { $null = Test-PSEnvironment -CheckAdmin -Exit $process | Stop-ProcessTree } } } } #EndRegion '.\Public\Stop-DevProcess.ps1' 79 #Region '.\Public\Stop-ProcessTree.ps1' -1 <# .SYNOPSIS Stops a process and all child processes that it spawned. .DESCRIPTION Stops a process and all child processes that it spawned. .PARAMETER ProcessId ID of the process to stop. .EXAMPLE Get-Process cmd | Stop-ProcessTree .LINK https://stackoverflow.com/a/55942155/14628263 .NOTES N/A #> function Stop-ProcessTree { [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(ValueFromPipelineByPropertyName = $true)] [Alias('Id')] [Int[]]$ProcessId ) begin { $script:CurrentProcess = Get-CimInstance -ClassName Win32_Process | Where-Object -FilterScript { $_.ProcessId -eq $PID } } process { foreach ($id in $ProcessId) { Get-CimInstance -ClassName Win32_Process | Where-Object -FilterScript { $_.ParentProcessId -eq $id -and ( $script:CurrentProcess.ProcessId, $script:CurrentProcess.ParentProcessId ) -notcontains $_.ProcessId } | ForEach-Object -Process { Stop-ProcessTree -ProcessId $_.ProcessId } $processToStop = Get-Process -Id $ProcessId -ErrorAction SilentlyContinue if ($processToStop) { <# Use for debugging unexpected kill behavior $processToStop | Format-Table -HideTableHeaders | Out-File -FilePath "$PSScriptRoot/Stop-ProcessTree.log" -Append #> $processToStop | Stop-Process -Force -PassThru | Wait-Process } } } } #EndRegion '.\Public\Stop-ProcessTree.ps1' 56 #Region '.\Public\Stop-Stopwatch.ps1' -1 <# .SYNOPSIS Stops a stopwatch started from Start-Stopwatch. .DESCRIPTION Stops a stopwatch started from Start-Stopwatch. .PARAMETER InputObject A stopwatch object from Start-StopWatch. .EXAMPLE $sw = Start-Stopwatch; $sw | Stop-Stopwatch .NOTES N/A #> function Stop-Stopwatch { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute( 'PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Stopping an in-memory stopwatch does not change external system state.' )] [OutputType([System.Diagnostics.Stopwatch])] [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [System.Object]$InputObject ) process { $InputObject.Stop() $InputObject } } #EndRegion '.\Public\Stop-Stopwatch.ps1' 35 #Region '.\Public\Test-CommandAvailable.ps1' -1 <# .SYNOPSIS Verifies a required CLI command is available on PATH. .DESCRIPTION Throws a clear, actionable error when the command cannot be found, rather than letting a later native command invocation fail with an unhelpful "command not found" error. .PARAMETER CommandName The command to check for. .PARAMETER ErrorMessage A custom error message. Defaults to a generic "not on PATH" message naming the command. .EXAMPLE Test-CommandAvailable -CommandName 'docker' .NOTES N/A #> function Test-CommandAvailable { [CmdletBinding()] param( [Parameter(Mandatory)] [String]$CommandName, [String]$ErrorMessage = ( "The '$CommandName' command is not on PATH. " + 'Install the CLI or add its bin directory to PATH.' ) ) if (-not (Get-Command -Name $CommandName -ErrorAction SilentlyContinue)) { throw $ErrorMessage } } #EndRegion '.\Public\Test-CommandAvailable.ps1' 37 #Region '.\Public\Test-IsAdmin.ps1' -1 <# .SYNOPSIS Tests whether the current console is running with admin priviledges. .DESCRIPTION Tests whether the current console is running with admin priviledges. .EXAMPLE Test-IsAdmin .NOTES N/A #> function Test-IsAdmin { [OutputType([Bool])] [CmdletBinding()] param () $currentUser = [Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent() $currentUser.IsInRole([Security.Principal.WindowsBuiltInRole]'Administrator') } #EndRegion '.\Public\Test-IsAdmin.ps1' 22 #Region '.\Public\Test-IsFileLocked.ps1' -1 <# .SYNOPSIS Short description .DESCRIPTION Long description .PARAMETER Path Parameter description .EXAMPLE An example .LINK https://mcpmag.com/articles/2018/07/10/check-for-locked-file-using-powershell.aspx .NOTES General notes #> function Test-IsFileLocked { [OutputType([Bool])] [CmdletBinding()] param ( [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] [Alias('FullName', 'PSPath', 'LiteralPath', 'Path')] [string[]]$FilePath ) process { foreach ($file in $FilePath) { $file = Convert-Path -Path $file if ([System.IO.File]::Exists($file)) { try { $filestream = [System.IO.File]::Open($file, 'Open', 'Write') $filestream.Close() $filestream.Dispose() $false } catch [System.UnauthorizedAccessException] { $true } catch { $true } } } } } #EndRegion '.\Public\Test-IsFileLocked.ps1' 49 #Region '.\Public\Test-IsNonInteractiveShell.ps1' -1 <# .SYNOPSIS Returns boolean determining if prompt was run non-interactively. .DESCRIPTION First, we check `[Environment]::UserInteractive` to determine if the shell is running interactively. An example of not running interactively would be if the shell is running as a service. If we are running interactively, we check the Command Line Arguments to see if the `-NonInteractive` switch was used; or an abbreviation of the switch. .LINK https://github.com/Vertigion/Test-IsNonInteractiveShell #> function Test-IsNonInteractiveShell { [CmdletBinding()] [OutputType([Boolean])] param () if ([Environment]::UserInteractive) { $commandLineArgs = [Environment]::GetCommandLineArgs() $isNonInteractive = $commandLineArgs -contains '-NonInteractive' $isVsCode = foreach ($arg in $commandLineArgs) { if ($arg -match "-HostProfileId\ 'Microsoft\.VSCode'") { $true } } } if ( $env:AGENT_JOBNAME -or ( $isNonInteractive -and -not $isVsCode ) ) { $true } else { $false } } #EndRegion '.\Public\Test-IsNonInteractiveShell.ps1' 42 #Region '.\Public\Test-PSEnvironment.ps1' -1 <# .SYNOPSIS Checks whether the current PowerShell environment is sufficient to run the script. .DESCRIPTION Checks whether the current PowerShell environment is sufficient to run the script by checking the installed version and whether it is being ran as an admin. .PARAMETER MinimumVersion Minimum version of PowerShell to check for. .PARAMETER MaximumVersion Minimum version of PowerShell to check for. .PARAMETER CheckAdmin Check to see whether the prompt is running as an administrator. .PARAMETER Exit Throws an error instead of returning $false. .EXAMPLE Test-PSEnvironment .EXAMPLE Test-PSEnvironment -CheckAdmin -Exit .NOTES N/A #> function Test-PSEnvironment { [OutputType([Bool])] [CmdletBinding()] param ( [AllowEmptyString()] [AllowNull()] [System.Object] $MinimumVersion = [System.Version]::new('5.1.0'), [AllowEmptyString()] [AllowNull()] [System.Object] $MaximumVersion, [Switch] $CheckAdmin, [Switch] $Exit ) $errMsg = @() if ($CheckAdmin -eq $true) { if ( Test-IsAdmin ) { Write-Verbose -Message 'Host is running with admin priviledges.' } else { $errMsg += 'Host is not running with admin priviledges.' } } $hostVersion = Get-PSVersion if ($MinimumVersion) { if ($MinimumVersion.GetType().Name -eq 'String') { $MinimumVersion = [System.Version]::new($MinimumVersion) } Write-Verbose -Message ( "Checking host version: $hostVersion against minimum " + "version $MinimumVersion." ) if ($hostVersion -lt $MinimumVersion) { $errMsg += ( "The minimum version of Windows PowerShell that is required by the script ($MinimumVersion) " + "does not match the currently running version ($hostVersion) of Windows PowerShell." ) } } if ($MaximumVersion) { if ($MaximumVersion.GetType().Name -eq 'String') { $MaximumVersion = [System.Version]::new($MaximumVersion) } Write-Verbose -Message ( "Checking host version: $hostVersion against maximum " + "version $MaximumVersion." ) if ($hostVersion -gt $MaximumVersion) { $errMsg += ( "The maximum version of Windows PowerShell that is required by the script ($MaximumVersion) " + "does not match the currently running version ($hostVersion) of Windows PowerShell." ) } } if ($errMsg) { $false if ($Exit) { throw $errMsg } else { Write-Host -Object $errMsg } } else { $true } } #EndRegion '.\Public\Test-PSEnvironment.ps1' 108 #Region '.\Public\Uninstall-ProgramByName.ps1' -1 <# .SYNOPSIS Uninstalls a program by name. .DESCRIPTION Uninstalls a program by name, instead of having to call its installer. .PARAMETER Name Name to search for. Accepts wildcard characters. Accepts pipeline input. .EXAMPLE Uninstall-ProgramByName -Name 'visual studio code' .NOTES General notes #> function Uninstall-ProgramByName { #[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingCmdletAliases', '')] [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [String]$Name, [Switch]$Wmi ) begin { $null = Test-PSEnvironment -MinimumVersion 5.1 -CheckAdmin -Exit } process { $Activity = "Uninstall $Name" if ($Wmi) { $status = 'Finding the installation...' Write-Progress -Activity $Activity -Status $status $attempts = 0 $app = @() do { $attempts++ $uninstallError = $false if ($attempts -gt 1) { Write-Progress -Activity $Activity -Status $status -CurrentOperation "Attempt: $attempts" } try { Write-Verbose -Message 'Gathering all installed apps...' $apps = Get-CimInstance -ClassName 'Win32_Product' -ErrorAction Stop Write-Verbose -Message "Finding $Name to uninstall..." $app += $apps | Where-Object { $_.Name -match [Regex]::Escape($Name) } } catch { Write-Verbose -Message "Attempt $attempts failed." Write-Verbose -Message $_ $uninstallError = $true } } while (($uninstallError -eq $true) -and ($attempts -le 3)) if (-not $app) { Write-Progress -Activity $Activity -Completed Write-Warning "$Name is either not installed, or can't be found by this function." } else { Write-Verbose -Message "Uninstalled $Name on attempt $attempts." } foreach ($instance in $app) { Write-Verbose -Message "Uninstalling $($instance.Name) $($instance.Version)..." Write-Progress -Activity $Activity -Status "Uninstalling $($instance.Name) $($instance.Version)..." Invoke-CimMethod -InputObject $instance -MethodName 'Uninstall' Write-Progress -Activity $Activity -Completed } } else { Write-Progress -Activity $Activity -Status 'Finding the installation...' $programLocations = @( 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall', 'HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall' ) $apps = Get-ChildItem -Path $programLocations | Get-ItemProperty | Sort-Object -Property DisplayName $app = @() $app += $apps | Where-Object { $_.DisplayName -match [Regex]::Escape($Name) } if (-not $app) { Write-Progress -Activity $Activity -Completed Write-Warning "$Name is either not installed, or can't be found by this function." } foreach ($instance in $app) { Write-Verbose -Message "Uninstalling $($instance.Name) $($instance.DisplayVersion)..." Write-Progress ` -Activity $Activity ` -Status "Uninstalling $($instance.Name) $($instance.DisplayVersion)..." $uninstallString = $instance.UninstallString $isExeOnly = Test-Path -LiteralPath $uninstallString if (-not $isExeOnly) { $uninstallString += ' /passive /norestart' # Need to explicitly set uninstall for installers that just call themselves again to uninstall $uninstallString = $uninstallString.Replace('/I', '/uninstall ') } $process = Start-Process -FilePath cmd -ArgumentList ('/c', $uninstallString) -PassThru $process | Wait-Process $process | Select-Object -Property ProcessName, ExitCode Write-Progress -Activity $Activity -Completed } } } } #EndRegion '.\Public\Uninstall-ProgramByName.ps1' 108 #Region '.\Public\Write-ConsoleMessage.ps1' -1 <# .SYNOPSIS Writes a formatted message to the host. .DESCRIPTION Two ways to style output: -ForegroundColor for direct color control with no other formatting, or -Intent for semantic meaning. The intent controls color and output-stream routing, while -Layout controls presentation and -Child selects the secondary form of that layout or intent. Legacy compound intents remain available while callers migrate to the separated parameters. .PARAMETER Message The message to display. .PARAMETER ForegroundColor Writes the message in this color with no other formatting. Mutually exclusive with -Intent. .PARAMETER Intent The semantic purpose of the message: Info, Action, Success, Warning, Caution, or Muted. Mutually exclusive with -ForegroundColor. Legacy compound values remain available for compatibility. .PARAMETER Layout The message presentation: Text, Heading, Usage, KeyValue, or Route. .PARAMETER Child Uses the child presentation for a heading, action, muted message, or usage line. .PARAMETER Label A label prefix, required when -Layout is KeyValue or Route. .PARAMETER NoNewline Suppresses the trailing newline. Not supported with -Intent Warning, since warnings use the warning stream rather than Write-Host. .EXAMPLE Write-ConsoleMessage 'Building...' -ForegroundColor Cyan .EXAMPLE Write-ConsoleMessage 'Build' -Layout Heading .EXAMPLE Write-ConsoleMessage 'Compiling case' -Intent Action -Child .NOTES N/A #> function Write-ConsoleMessage { [CmdletBinding(DefaultParameterSetName = 'Intent')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute( 'PSAvoidUsingWriteHost', '', Justification = 'The intended purpose of this function is formatted host output.' )] param( [Parameter(Position = 0)] [AllowEmptyString()] [String]$Message = '', [Parameter(ParameterSetName = 'Color')] [ConsoleColor]$ForegroundColor, [Parameter(ParameterSetName = 'Intent')] [ValidateSet( # Primary intents 'Info', 'Action', 'Success', 'Warning', 'Caution', 'Muted', # Compatibility intents 'Default', 'Header', 'SubHeader', 'ActionDetail', 'Detail', 'Endpoint', 'MutedDetail', 'Route', 'Usage', 'UsageStep' )] [String]$Intent = 'Info', [Parameter(ParameterSetName = 'Intent')] [ValidateSet( 'Text', 'Heading', 'Usage', 'KeyValue', 'Route' )] [String]$Layout = 'Text', [Parameter(ParameterSetName = 'Intent')] [Switch]$Child, [Parameter(ParameterSetName = 'Intent')] [String]$Label, [Switch]$NoNewline ) if ($PSCmdlet.ParameterSetName -eq 'Color') { $writeHostParameters = @{ Object = $Message } if ($PSBoundParameters.ContainsKey('ForegroundColor')) { $writeHostParameters.ForegroundColor = $ForegroundColor } if ($NoNewline) { $writeHostParameters.NoNewline = $true } Write-Host @writeHostParameters return } $effectiveIntent = $Intent $effectiveLayout = $Layout $effectiveChild = $Child.IsPresent # Compatibility mappings for the original compound intent vocabulary. switch ($Intent) { 'Default' { $effectiveIntent = 'Info' } 'Header' { $effectiveIntent = 'Info' $effectiveLayout = 'Heading' } 'SubHeader' { $effectiveIntent = 'Info' $effectiveLayout = 'Heading' $effectiveChild = $true } 'ActionDetail' { $effectiveIntent = 'Action' $effectiveChild = $true } 'Detail' { $effectiveIntent = 'Info' $effectiveChild = $true } 'Endpoint' { $effectiveIntent = 'Info' $effectiveLayout = 'KeyValue' } 'MutedDetail' { $effectiveIntent = 'Muted' $effectiveChild = $true } 'Route' { $effectiveIntent = 'Muted' $effectiveLayout = 'Route' } 'Usage' { $effectiveIntent = 'Info' $effectiveLayout = 'Usage' } 'UsageStep' { $effectiveIntent = 'Info' $effectiveLayout = 'Usage' $effectiveChild = $true } } if ($effectiveIntent -eq 'Warning') { if ($NoNewline) { throw '-NoNewline is not supported with -Intent Warning because warnings use the warning stream.' } Write-Warning -Message $Message return } $intentColor = switch ($effectiveIntent) { 'Info' { $null } 'Action' { 'Cyan' } 'Success' { 'Green' } 'Caution' { 'Yellow' } 'Muted' { 'DarkGray' } } if ($effectiveLayout -eq 'Heading') { $intentColor = if ($effectiveChild) { 'DarkCyan' } else { 'Cyan' } } elseif ($effectiveLayout -eq 'KeyValue') { $intentColor = 'Gray' } elseif ($effectiveLayout -eq 'Route') { $intentColor = 'DarkGray' } elseif ( $effectiveLayout -eq 'Text' -and $effectiveChild -and $effectiveIntent -in @('Info', 'Action') ) { $intentColor = 'Gray' } if ($effectiveLayout -in @('KeyValue', 'Route') -and -not $Label) { $labelContext = if ($Intent -in @('Endpoint', 'Route')) { $Intent } else { $effectiveLayout } throw "-Label is required when using $labelContext." } if ($effectiveLayout -eq 'Heading' -and -not $effectiveChild) { Write-ConsoleMessage $Message = "=== $Message ===" } elseif ( $effectiveIntent -eq 'Success' -and $effectiveLayout -eq 'Text' -and -not $effectiveChild -and -not $NoNewline ) { Write-ConsoleMessage } elseif ($effectiveLayout -eq 'Usage') { $indent = if ($effectiveChild) { ' ' } else { ' ' } $Message = "$indent$Message" } elseif ($effectiveLayout -eq 'KeyValue') { $Message = ' {0,-19}{1}' -f ('{0}:' -f $Label), $Message } elseif ($effectiveLayout -eq 'Route') { $Message = ' {0,-15}-> {1}' -f $Label, $Message } elseif ($effectiveLayout -eq 'Text' -and $effectiveChild) { $indent = if ($effectiveIntent -eq 'Muted') { ' ' } else { ' ' } $Message = "$indent$Message" } $writeHostParameters = @{ Object = $Message } if ($intentColor) { $writeHostParameters.ForegroundColor = $intentColor } if ($NoNewline) { $writeHostParameters.NoNewline = $true } Write-Host @writeHostParameters } #EndRegion '.\Public\Write-ConsoleMessage.ps1' 246 #Region '.\Public\Write-ProgressToHost.ps1' -1 <# .SYNOPSIS Use in place of Write-Progress to output the content to the console instead of a progress bar. #> function Write-ProgressToHost { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingCmdletAliases', '')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')] [CmdletBinding()] param( [Parameter(Position = 0, Mandatory = $true)] [String]$Activity, [String]$Status, [Int32]$Id, [Int32]$PercentComplete, [Int32]$SecondsRemaining, [String]$CurrentOperation, [Int32]$ParentId, [Switch]$Completed, [Int32]$SourceId ) $paramOutputOrder = @( 'Activity', 'Status', 'PercentComplete', 'CurrentOperation', 'SecondsRemaining', 'Completed' ) $params = $paramOutputOrder | Where-Object -FilterScript { $PSBoundParameters.Keys -contains $_ } $paramValues = $params | ForEach-Object -Process { switch ($_) { 'PercentComplete' { $percent = '[' $progressByTen = [Math]::Floor($PercentComplete / 10) for ($i = 0; $i -lt $progressByTen; $i++) { $percent += '#' } for ($i = 0; $i -lt 10 - $progressByTen; $i++) { $percent += ' ' } $percent += ']' $percent } 'SecondsRemaining' { "-$($SecondsRemaining)s" } 'Completed' { 'Completed' } default { $PSBoundParameters[$_] } } } $message = '' if ($env:AGENT_JOBNAME) { $message += '##[info] ' } $message += ($paramValues -join ' | ') if ($ProgressPreference -eq 'Continue') { Write-ConsoleMessage -Message $message -ForegroundColor DarkGray } else { Write-Verbose -Message $message } } #EndRegion '.\Public\Write-ProgressToHost.ps1' 67 |