Saturday, October 1, 2022
HomePowershellOn Preferences and Scopes - PowerShell Group

On Preferences and Scopes – PowerShell Group


Progress in PowerShell: a story of Verbosity and different preferences with classes in Scopes and Proxies thrown in

It began, as these items typically do, with somebody complaining. In PowerShell Model 7.2 the output of Invoke-WebRequest -Verbose and Invoke-RestMethod -Verbose seem like this:

VERBOSE: GET with 0-byte payload

In all the sooner variations they seem like the model beneath, which is extra useful whenever you’re attempting to debug code that builds URIs:

VERBOSE: GET https://google.com/ with 0-byte payload

A proxy perform will repair that. If two instructions have the identical title an alias beats a perform, which beats a cmdlet, which beats an exterior program. You possibly can specify the total path to an exterior program or cmdlet – for instance Microsoft.PowerShell.UtilityInvoke-RestMethod so an Invoke-RestMethod perform can act as a proxy for the cmdlet, something which calls Invoke-RestMethod will go to the perform, which calls the cmdlet with its totally certified title. PowerShell even has a mechanism to create the perform’s code:

$cmd      = Get-Command Invoke-RestMethod
$MetaData = New-Object System.Administration.Automation.CommandMetaData $cmd
[System.Management.Automation.ProxyCommand]::create($MetaData) | clip

(I don’t carry these 3 traces in my head, once I want them I consult with a script Jeffrey Snover wrote way back, a more moderen model is on the PowerShell Gallery.)
I added additional Write-Verbose calls and tidied up the autogenerated code and posted the end result as a gist.
A module I’m working has a lot of calls to Invoke-RestMethod however the proxy perform wouldn’t see that I’d specified -Verbose. So I wanted to analyze.

The -Verbose change units the worth of $VerbosePreference within the perform being referred to as; if you happen to thought setting the worldwide $VerbosePreference to proceed was the identical as a operating every thing with -Verbose, attempting the next may shock you:

$VerbosePreference="Proceed"
Invoke-WebRequest "https://google.com" -OutFile delete.me
Copy-Merchandise delete.me delete.too

Invoke-WebRequest heeds the choice, however Copy-Merchandise solely prints a message if the -Verbose change is specified.

My proxy perform would print a verbose message if run with -Verbose, it would heed the worldwide preference-variable however not the change handed to the perform that referred to as it. The -Affirm, -Debug, -ErrorAction, -InformationAction, -Verbose, -WarningAction, and -WhatIf switches all set the corresponding preference-variable inside a perform however that wasn’t being inherited when the features in my module referred to as the proxy perform.
I might reproduce this with the 2 features beneath. First, I loaded them from a single .PSM1 file (and issues had been the identical if I pasted them in at a PowerShell immediate)

perform one {
  [cmdletBinding()]
  param()
  Write-Verbose "One calls two"
  two
  Write-Verbose "Two returned"
}

perform two {
  [cmdletBinding()]
  param()
  Write-Verbose "Two has a message"
}

If I load this, I get three messages:

VERBOSE: One calls two
VERBOSE: Two has a message
VERBOSE: Two returned

Issues change if One is in its personal module

Screen shot showing the difference if the calling function is a module

One -verbose now simply produces two messages:

VERBOSE: One calls two
VERBOSE: two returned

Setting the worldwide preference-variable on the immediate returns all 3 – as a result of perform two sees it.
It’s common to imagine that a perform inherits the variables from no matter referred to as it; we are able to see that working with less complicated features

perform three {
    $e="ewe"
    4
}

perform 4 {
    $e
} 

If I set $e and run three from the immediate

> $e = "eye"  
> three
ewe

the idea holds; and a variety of documentation stops there, but when I import three from a module

> three
eye

The inheritance assumption is certified by a rule that claims what occurs in a module stays within the module.
The query is what can we do about it?

Earlier than it dawned on me that this was a scopes factor and never simply -Verbose, a search introduced me a clue; the answer is to alter perform two (or my proxy perform) as follows:

perform two {
  [cmdletBinding()]
  param($VerbosePreference = $PSCmdlet.GetVariableValue('VerbosePreference')
  Write-Verbose "Two has a message"
}

We will solely use $PSCmdlet if we’ve got both [cmdletBinding()] or a [parameter()] ornament – only a aspect notice on that, if you happen to paste in

perform 5 { param ($p) }
perform six  { param ([parameter()]$p) }

whenever you attempt to tab-complete parameters for 5 and six, you’ll see six will get all of the frequent parameters however 5 doesn’t – [parameter()] is an implicit [cmdletBinding()], though it’s nonetheless good to jot down the latter explicitly.

$PSCmdlet is offered when the perform is organising its variables. Contained in the perform we’d by no means exchange $x with the long-winded $PSCmdlet.GetVariableValue("x"), however in a parameter it’s “use the worth from the scope that referred to as you – even when that scope is a module”.

Now the referred to as perform inherits the choice from its caller. If we specify -Verbose it takes priority, so nothing breaks copying in $VerbosePreference. The identical factor applies to -Affirm and -WhatIf.

perform seven {
  [cmdletBinding(SupportsShouldProcess=$true)]
  param()
  eight
  Write-Host "One thing Secure "
}

perform eight {
  [cmdletBinding(SupportsShouldProcess=$true)]
  param()
  if ($PSCmdlet.ShouldProcess("This","Do you wish to do")) {
    Write-Host "One thing harmful"
  }
}

If seven and eight are loaded from the identical psm1 file

seven -Affirm

Affirm
Are you certain you wish to carry out this motion?
Performing the operation "Do you wish to do" on track "This".
[Y] Sure [A] Sure to All [N] No [L] No to All [S] Droop [?] Assist (default is"Y"): n

One thing Secure

However, if seven hundreds from its personal module …

seven -Affirm
One thing harmful
One thing Secure

Modifying the parameters in eight with the code beneath restores the affirmation

param($ConfirmPreference = $PSCmdlet.GetVariableValue('ConfirmPreference'))

-confirm units $confirmPreference to low which triggers $PSCmdlet.ShouldProcess to immediate the consumer. I deal with -Power as an additional choice and my features sometimes have
if ($Power -or $PSCmdlet.ShouldProcess(...
to make sure the a command might be run non-interactively, even when its affect is ready larger than $ConfirmPreference.

The instance above may lead you to assume -Affirm and -WhatIf ought to be inherited, however this may trigger an issue with scopes on the script stage (which apply module-wide). Suppose the module that contained seven learn like this:

$ConfirmPreference="None"

perform seven {
...
}

Any cmdlet that will usually ask for affirmation inside seven will run silently. However what if the module containing eight units that choice to one thing totally different? If a perform overrides one thing from its personal script-scope, then solely issues which share that scope see the change, which appears logical. Pondering of features’ scopes as youngsters of their module’s scope which in flip is often a toddler of the worldwide scope means we are able to say issues go down their department of the scope “tree” however don’t soar between branches.

Earlier than leaving $ConfirmPreference, I’ve been take into consideration altering it inside a perform, so it drops to low if many gadgets are being up to date. I’m undecided that’s an excellent thought as a result of coming to rely on a immediate and which isn’t there reliably, will result in bother.

The title mentioned that is about Progress. PowerShell 7.2 has an non-obligatory new method of displaying the progress bar, however the issues it depends on are a bit flaky within the Built-in-Shell in Visible Studio Code (issues enhance if you happen to use Write-Progress -completed) and Invoke-WebRequest downloading 100 log recordsdata turns into a large number. There isn’t any -Progress frequent parameter, however the day earlier than I began on the -Verbose drawback, I had discovered that including $ProgressPreference as a parameter labored, so it is sensible to do it the identical method. I inserted the next parameter into the perform that calls Invoke-WebRequest. Not like the examples above, which may be tagged with [Parameter(DontShow)] I need this to tab-complete the doable values, so it’s marked with the ActionPreference kind:

[ActionPreference]$ProgressPreference = $PSCmdlet.GetVariableValue('ProgressPreference')

And I may also need that to inherit into my proxy perform.
With that in place, I can use the code beneath to name my perform and exchange the byte-by-byte progress indicator that Invoke-WebRequest shows with my very own file-by-file one:

$rely = 0
ForEach ($f in $file) {
  Write-Progress "Downloading" -PercentComplete ($rely/$file.Depend)
  Myfunction $f -ProgressPreference SilentlyContinue
  $rely += 100
}
Write-Progress "Downloading" -Accomplished

Fast tip: including 100 to the counter every time (as an alternative of including 1) removes the necessity to multiply by 100 for a share.

Since I discussed ErrorAction above, earlier than ending I wished to share one final tip about preferences:

perform 9 {
  [cmdletBinding()]
  param ($Identify)
  if (-not $Identify) {throw "Identify is required"}
  Write-Host "Deleting $Identify*"
}

The “harmful” line within the instance above by no means runs if $n is empty, proper? Flawed, truly.

Screen shot showing the effect of -ErrorAction on throw

Specifying -ErrorAction prevents the throw assertion throwing so

9 "" -ErrorAction SilentlyContinue would imply the harmful code is run.

When it’s appearing as a “fence” round harmful code, it’s value placing a return after throw.

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments