collection of preference variables in Powershell - powershell

I'm looking into importing my entire set of preference variables into a remote scope. Does PowerShell implement collections of variables? Is there a collection of preference variables? If so, would I be able to import the collection with $using: ?
Something like:
Begin {
$scriptblock = {
Try {
$VerbosePreference = $Using:VerbosePreference
$ErrorActionPreference = $Using:ErrorActionPreference
...
}
Catch{ #Ignore these errors }
...
I'd like to import all the preference variables without specifying individually if possible...

PowerShell (as of v5.1):
doesn't implement collections of variables,
and there is no programmatic way to unambiguously and exhaustively identify all preference variables.
For the current list of all preference variables, see Get-Help about_Preference_Variables.
That said, you can use wildcard expression *Preference with Get-Variable to locate at least some of the preference variables - and perhaps they cover your needs:
> (Get-Variable *Preference).Name
ConfirmPreference
DebugPreference
ErrorActionPreference
InformationPreference
ProgressPreference
VerbosePreference
WarningPreference
WhatIfPreference
As stated, the results are neither guaranteed to be complete (e.g., MaximumHistoryCount is not matched), nor do they exclude potential false positives (nothing prevents you from defining variable $FooPreference, for instance).
If you're willing to extract all variable names from about_Preference_Variables help topic - which is not fully robust - see the bottom of this post.
Overall, the best approximation is probably the following command:
> Get-Variable |
Where-Object {
$_.Name -clike '*Preference' -or
($_.Attributes -and $_.Options -notcontains 'ReadOnly')
} | % Name
ConfirmPreference
DebugPreference
ErrorActionPreference
InformationPreference
MaximumAliasCount
MaximumDriveCount
MaximumErrorCount
MaximumFunctionCount
MaximumHistoryCount
MaximumVariableCount
OutputEncoding
ProgressPreference
PSDefaultParameterValues
VerbosePreference
WarningPreference
WhatIfPreference
This relies on the assumption that only preference variables have (validation) attributes, which is typically true, but, again, you're free to define your own variables with validation attributes, which would then mistakenly be included too.
$_.Options -notcontains 'ReadOnly' weeds out read-only variables, because, by definition, they can't be preference variables if they cannot be modified.
As for using these variables in a remote command / a background job:
There is no elegant solution (and $using: only works with literal variable names), but you can try the following:
# Collect pref. variables, to the best of our ability.
$prefVarDefs = Get-Variable | ? { $_.Name -clike '*Preference' -or ($_.Attributes -and $_.Options -notcontains 'ReadOnly') }
# Pass them to the background/remote script block and have them
# assigned there.
Start-Job { $args | % { set-variable $_.Name $_.Value }; ... } -Args $prefVarDefs
Note that Start-Job is used as an example (because it can be run without remoting and in a non-elevated session), but the same rules apply as with Invoke-Command, for instance, and the same technique can be used with the latter.
Another option is to parse the about_Preference_Variables help topic, which is somewhat brittle however, because it:
relies on the topic's specific formatting
relies on the topic to be both complete and accurate.
(Get-Help about_Preference_Variables) -creplace '(?s)\A.*?\r?\n +?Variable +Default Value\r?\n +?-+ +-+\r?\n(.+?)\r?\n\r?\n.*\Z', '$1' -split '\r?\n' | % { (-split $_)[0] }

Related

Powershell Different color in strings with echo in array

I trying to write a script, which show iis pools state with a different color.
And I can't understand why script coloring in one color all strings when I use echo.
Here script:
$pools = invoke-command -session $session -scriptblock {Import-Module WebAdministration; Get-ChildItem IIS:\AppPools | Where {$_.Name -like "*abc*"}}
$poolsShow = $pools | Select-Object -Property name, state
$poolsShow | ForEach-Object {
if($_.state -eq "Started") {
$Host.UI.RawUI.ForegroundColor = "Green";
echo $_;
[Console]::ResetColor();
}
if($_.state -eq "Stopped") {
$Host.UI.RawUI.ForegroundColor = "Red";
echo $_;
[Console]::ResetColor();
}
}
It is work if I go through the $pools, but if I select name and state via Select-Object - all strings are coloring in the color of the last service.
I have tried via Write-Host - and it's worked, but I didn't find a way, how to format output in one table with a headers only at first line and with the same width in every string.
You can take a similar approach as the one proposed in this answer, the only difference would be that the ANSI Escape Sequences are prepended to the property values of the objects created by Select-Object. Helpful answer provided by #mklement0 in the same question provides more details on this.
function status {
$ansi = switch($args[0]) {
Stopped { "$([char] 27)[91m" }
Started { "$([char] 27)[32m" }
}
$ansi + $args[0]
}
Invoke-Command -Session $session -ScriptBlock {
Import-Module WebAdministration
Get-ChildItem IIS:\AppPools | Where-Object { $_.Name -like "*abc*" }
} | Select-Object Name, #{ N='Status'; E={ status $_.State }}
A demo using custom objects:
0..5 | ForEach-Object {
[pscustomobject]#{
Name = "Test $_"
Status = ('Started', 'Stopped')[$_ % 2]
}
} | Select-Object Name, #{ N='Status'; E={ status $_.Status }}
To complement Santiago's helpful answer:
As for what you tried:
echo in PowerShell is a built-in alias for Write-Output, which does not print directly to the console - instead, it prints to the success output stream.
If the success output stream isn't captured or redirected in a given command, it is eventually printed to the console, after undergoing for-display formatting by PowerShell's formatting system.
Because your output objects have 4 or fewer properties, PowerShell applies tabular formatting by default; that is, the Format-Table cmdlet is implicitly used, which has a perhaps surprising implication:
So as to allow determining suitable column widths for the table, a 300-millisecond delay is introduced during which the objects are internally cached and analyzed.
While this behavior is helpful in principle, it has surprising side effects, notably in that direct-to-host output and output from other streams then can appear out of order; a simple example: [pscustomobject] #{ foo = 1 }; Write-Host 'Why am I printing first?? - see this answer for background information.
Therefore, the formatted table's rows only started printing after that delay, so your attempt to control their color one by one with ForEach-Object was ineffective.
As an aside: In PowerShell (Core) 7.2+ there's an additional consideration: formatted output now applies its own coloring by default, as controlled by the .OutputRendering property of the $PSStyle preference variable.
Santiago's answer bypasses this problem by using a calculated property to color individual property values rather than trying to control the coloring of the already-formatted representation of the object.
If you want a prepackaged, general-purpose solution, you can use the Out-HostColored function from this Gist (authored by me), which in your case would make the solution as simple as piping your objects to Out-HostColored.ps1 #{ Started = 'Green'; Stopped = 'Red' }:
# Download and define function `Out-HostColored` on demand (will prompt).
# To be safe, inspect the source code at the specified URL first.
if (-not (Get-Command -ErrorAction Ignore Out-HostColored1)) {
$gistUrl = 'https://gist.github.com/mklement0/243ea8297e7db0e1c03a67ce4b1e765d/raw/Out-HostColored.ps1'
if ((Read-Host "`n====`n OK to download and define function ``Out-HostColored```n from Gist ${gistUrl}?`n=====`n(y/n)?").Trim() -notin 'y', 'yes') { Write-Warning 'Aborted.'; exit 2 }
Invoke-RestMethod $gistUrl | Invoke-Expression 3>$null
if (-not ${function:Out-HostColored}) { exit 2 }
}
# Emit sample objects and color parts of their formatted representation
# based on regex patterns.
0..5 | ForEach-Object {
[pscustomobject]#{
Name = "Test $_"
State = ('Started', 'Stopped')[$_ % 2]
}
} |
Out-HostColored.ps1 #{ Started = 'Green'; Stopped = 'Red' }
Output:
Add -WholeLine if you want to color matching lines in full.
The hashtable maps search text patterns to colors.
Whenever a pattern is found in the formatted representations of the input objects, it is colored using the specified color.
Note that this means that finding what to color is purely based on string parsing, not on OOP features (such as checking specific properties).
Note that the hashtable keys are regexes by default, unless you also specify
-SimpleMatch.
Thus you could easily make the above more robust, if needed, such as replacing Started = with '\bStarted\b' = in order to only match full words.

Return boolean from string search

I'm trying to return TRUE from searching Get-ComplianceSearch's output for 'Completed'. My code below is a simple wait loop. But I don't think I'm returning the value correctly because the loop never finishes. I'm fairly new to PowerShell. Please assist or direct.
I'm using Powershell Core 7.1. There are no errors but the Search-String condition never returns TRUE.
try {
$timer = [Diagnostics.Stopwatch]::StartNew()
while (($timer.Elapsed.TotalSeconds -lt $Timeout) -and (-not (Get-ComplianceSearch -
Identity $searchName | Select-String 'Completed' -SimpleMatch -Quiet))) {
Start-Sleep -Seconds $RetryInterval
$totalSecs = [math]::Round($timer.Elapsed.TotalSeconds, 0)
Write-Verbose -Message "Still waiting for action to complete after [$totalSecs]
seconds..."
}
$timer.Stop()
if ($timer.Elapsed.TotalSeconds -gt $Timeout) {
throw 'Action did not complete before timeout period.'
} else {
Write-Verbose -Message 'Action completed before timeout period.'
}
} catch {
Write-Error -Message $_.Exception.Message
}
(This is the expected output of the command Get-ComplianceSearch)
Okay, you don't want to use Select-String here (although you can, see #mklement0's helpful answer, looking at object properties is usually preferred). That is returning an object and you want to check the Status property for "Completed". Make the following change to the -not subexpression:
(-not (Get-ComplianceSearch -Identity $searchName | Where-Object {
$_.Status -eq 'Completed'
}))
The above can be on one line but I broke it up for readability.
Basically, Select-String looks for content in strings. If you are looking for a particular value of an object property however, you can use Where-Object to test for a condition and return any objects matching that condition. In this case, we want to return any object that have a Status of 'Completed', so we can negate that in the if statement.
You (or others) might be wondering how this works since Where-Object returns matching objects, but not booleans. The answer is "truthiness". PowerShell objects are "truthy", which means anything can be evaluated as a [bool].
The following values evaluate to $false in most cases. I've included some gotchas to watch out for when relying on "truthy" values:
A numeric value of 0
A string value of 0 evaluates as $true
Empty arrays
Empty strings
A whitespace-only string or strings consisting only of non-printable characters evaluates as $true
$false
A string value of False evaluates as $true
Most everything else will evaluate to $true. This is also why comparison operators are syntactically optional when checking whether a variable is $null or not. Although there are times when an explicit value check is a good idea as comparison operators compare the actual values instead of only whether the variable "is" or "isn't".
How does this apply to the expression above then? Simple. if statements, always treat the condition expression as a [bool], no conversion required. In addition, logical operators and conditional operators also imply a boolean comparison. For example, $var = $obj assigns $obj to $var, but$var = $obj -eq $obj2 or $var = $obj -and $obj2 will assign $true or $false.
So knowing the above, if Where-Object returns nothing, it's $false. If it returns a tangible object, it's $true.
Bender the Greatest's helpful answer shows a better alternative to using Select-String, because OO-based filtering that queries specific properties is always more robust than searching string representations.
That said, for quick-and-dirty interactive searches, being able to search through a command's formatted display output can be handy, and, unfortunately, Select-String does not do that by default.
As for what you tried:
To make your Select-String work, you need to insert Out-String -Stream before the Select-String call, so as to ensure that the for-display representation is sent through the pipeline, line by line.
# `oss` can be used in lieu of `Out-String -Stream` in PSv5+.
# `sls` can be used in lieu of `Select-String`.
Get-ComplianceSearch | Out-String -Stream | Select-String 'Completed' -SimpleMatch -Quiet
Note:
If you want to search a for-display representation other than the default one, you can insert a Format-* cmdlet call before the Out-String -Stream segment; e.g.
Get-Item / | Format-List * | Out-String -Stream | Select-String ... would search through a list representation of all properties of the object output by Get-Item.
Perhaps surprisingly, Select-String does not search an input object's for-display representation, as you would see it in the console, using the rich formatting provided by PowerShell's display-formatting system.
Instead, it performs simple .ToString() stringification, whose results are often unhelpful and cannot be relied upon to include the values of properties. (E.g.,
#{ foo = 'bar' } | Select-String foo does not work as intended; it is equivalent to
#{ foo = 'bar' }.ToString() | Select-String foo and therefore to
'System.Collections.Hashtable' | Select-String foo
Arguably, Select-String should always have defaulted to searching through the input objects' formatted string representations:
That there is demand for this behavior is evidenced by the fact that PowerShell versions 5 and above (both editions) ship with the oss convenience function, which is a wrapper for Out-String -Stream.
GitHub issue #10726 asks that the current behavior of Select-String be changed to search the for-display string representations by default.

How to "alias" with a bound parameter without breaking pipelining in Powershell?

I find myself often tacking |select -first 10 onto the end of commands, and I'd like to shorten that to |s10, |s50, and a couple other variants. So I'd like to do the equivalent of set-alias s10 select-object -first 10.
The standard way you "alias" with bound parameters is to write a function and forward #args along with the extra params. But if I write a function that pipes $input through select-object, I lose streaming.
I could write a begin/process/end function, but I don't know if/how I can forward each of those to equivalents in select-object. I could write my own begin/process/end implementation of select-object that just implements the -first behavior, but that's just wrong...
(My fallback is to add a tab-completion to expand s10, but I'd really rather learn how I can implement a proper function.)
How can I implement a function that forwards to select-object with a parameter I want added but doesn't break pipelining?
I found this source https://blogs.technet.microsoft.com/heyscriptingguy/2011/03/01/proxy-functions-spice-up-your-powershell-core-cmdlets/
In this case, a shortcut/alias for Select-String -First 10, it comes down to:
$metadata = New-Object System.Management.Automation.CommandMetaData (Get-Command Select-Object)
[System.Management.Automation.ProxyCommand]::Create($MetaData) | Out-File -FilePath prxyfunctions.psm1
Open the prxyfunctions.psm1 module file and wrap the complete content in the new function called S10
function S10 {
[CmdletBinding(DefaultParameterSetName = 'DefaultParameter', HelpUri = 'https://go.microsoft.com/fwlink/?LinkID=113387', RemotingCapability = 'None')]
param(
<abbreviated...>
.ForwardHelpTargetName Microsoft.PowerShell.Utility\Select-Object
.ForwardHelpCategory Cmdlet
#>
}
Then in the Begin{} section add one statement $PSBoundParameters.Add('First','10') like below.
begin {
try {
$outBuffer = $null
if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer)) {
$PSBoundParameters['OutBuffer'] = 1
}
$PSBoundParameters.Add('First','10')
$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Utility\Select-Object', [System.Management.Automation.CommandTypes]::Cmdlet)
$scriptCmd = {& $wrappedCmd #PSBoundParameters }
$steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
$steppablePipeline.Begin($PSCmdlet)
}
catch {
throw
}
}
That's it. Save the file, import the module, type a nice short command like gci c:\|s10 and get just 10 results.
If you really want to make things error proof, more coding is needed. If S10 -First 2 is used you'll get a nice error thrown.
EDIT in response to #PetSerAl 's useful comments
Some cmdlets further in the pipeline might not be able to handle the proxied function, for instance Sort-Object. Compare the output of these two lines
-join (20..1 | Select -First 10 | Sort)
11121314151617181920
-join (20..1 | S10 | Sort)
<nothing>
-join (20..1 | S10 -Wait | Sort)
11121314151617181920
It is possible to work around that by using the -Wait parameter on the commandline. Or code the Wait parameter in the proxy function $PSBoundParameters.Add('Wait',$true)
When working with large collections this is unfortunate because it disables the Select-Object feature that stops the pipeline after x elements, resulting in more processing and longer waiting.
never use aliases in production scripts is what I would say (and is considered best practice). If its a bit of test code or something quick and dirty that no-one else will ever use, fair enough, but never in production scripts. Aliases can be removed, changed to run other commands and leave you with unintended results as they are user specific.

Powershell Switch Parameter to add to end of expression

Here's what I'm trying to do:
param([Switch]$myparameter)
If($myparamter -eq $true) {$export = Export-CSV c:\temp\temp.csv}
Get-MyFunction | $export
If $myparameter is passed, export the data to said location. Else, just display the normal output (in other words, ignore the $export). What doesn't work here is setting $export to the "Export-csv...". Wrapping it in quotes does not work.
I'm trying to avoid an if, then statement saying "if it's passed, export this. If it's not passed, output data"
I have a larger module that everything works in so there is a reason behind why I am looking to do it this way. Please let me know if any additional information is needed.
Thank you everyone in advance.
tl;dr:
param([Switch] $myparameter)
# Define the core command as a *script block* (enclosed in { ... }),
# to be invoked later, either with operator . (no child variable scope)
# or & (with child variable scope)
$scriptBlock = { Get-MyFunction }
# Invoke the script block with . (or &), and pipe it to the Export-Csv cmdlet,
# if requested.
If ($myparameter) { # short for: ($myparameter -eq $True), because $myparameter is a switch
. $scriptBlock | Export-Csv c:\temp\temp.csv
} else {
. $scriptBlock
}
TessellatingHeckler's answer is concise, works, and uses a number of advanced features cleverly - however, while it avoids an if statement, as requested, doing so may not yield the best or most readable solution in this case.
What you're looking for is to store a command in a variable for later execution, but your own attempt to do so:
If ($myparameter -eq $true) { $export = Export-CSV c:\temp\temp.csv }
results in immediate execution, which is not only unintended, but fails, because the Export-Csv cmdlet is missing input in the above statement.
You can store a snippet of source code for later execution in a variable via a script block, simply by enclosing the snippet in { ... }, which in your case would mean:
If ($myparameter -eq $true) { $export = { Export-Csv c:\temp\temp.csv } }
Note that what you pass to if is itself a script block, but it is by definition one that is executed as soon as the if condition is found to be true.
A variable containing a script block can then be invoked on demand, using one of two operators:
., the "dot-sourcing" operator, which executes the script block in the current scope.
&, the call operator, which executes the script block in a child scope with respect to potential variable definitions.
However, given that you only need the pipeline with an additional command if switch $myparameter is specified, it's better to change the logic:
Store the shared core command, Get-MyFunction, in a script block, in variable $scriptBlock.
Invoke that script block in an if statement, either standalone (by default), or by piping it to Export-Csv (if -MyParameter was specified).
I'm trying to avoid an if, then statement
Uh, if you insist...
param([Switch]$myparameter)
$cmdlet, $params = (('Write-output', #{}),
('Export-Csv', #{'LiteralPath'='c:\temp\temp.csv'}))[$myparameter]
Get-MyFunction | & $cmdlet #params

Writing $null to Powershell Output Stream

There are powershell cmdlets in our project for finding data in a database. If no data is found, the cmdlets write out a $null to the output stream as follows:
Write-Output $null
Or, more accurately since the cmdlets are implemented in C#:
WriteOutput(null)
I have found that this causes some behavior that is very counter to the conventions employed elsewhere, including in the built-in cmdlets.
Are there any guidelines/rules, especially from Microsoft, that talk about this? I need help better explaining why this is a bad idea, or to be convinced that writing $null to the output stream is an okay practice. Here is some detail about the resulting behaviors that I see:
If the results are piped into another cmdlet, that cmdlet executes despite no results being found and the pipeline variable ($_) is $null. This means that I have to add checks for $null.
Find-DbRecord -Id 3 | For-Each { if ($_ -ne $null) { <do something with $_> }}
Similarly, If I want to get the array of records found, ensuring that it is an array, I might do the following:
$recsFound = #(Find-DbRecord -Category XYZ)
foreach ($record in $recsFound)
{
$record.Name = "Something New"
$record.Update()
}
The convention I have seen, this should work without issue. If no records are found, the foreach loop wouldn't execute. Since the Find cmdlet is writing null to the output, the $recsFound variable is set to an array with one item that is $null. Now I would need to check each item in the array for $null which clutters my code.
$null is not void. If you don't want null values in your pipeline, either don't write null values to the pipeline in the first place, or remove them from the pipeline with a filter like this:
... | Where-Object { $_ -ne $null } | ...
Depending on what you want to allow through the filter you could simplify it to this:
... | Where-Object { $_ } | ...
or (using the ? alias for Where-Object) to this:
... | ? { $_ } | ...
which would remove all values that PowerShell interprets as $false ($null, 0, empty string, empty array, etc.).