Exploring PowerShell: Common Parameters, Variables, and More!

When writing PowerShell functions and scripts, you might come across a need to identify common parameters, automatic variables, or other details that can change depending on the environment.

It turns out that PowerShell offers a number of tools to explore, from the standard Get-Command, Get-Help, and Get-Member Cmdlets, to the .NET Framework, with tools like reflection and abstract syntax trees.

This past week I received an interesting question on Invoke-Parallel; can we import variables and modules from the end user’s session to make the command more approachable? This was a good question – Invoke-Parallel is widely used by my co-workers, but there is often confusion over the idea of each runspace being an independent environment.

Before we dive into this, let’s take a step back and look at some work from Bruce Payette, “a co-designer of the PowerShell language and the principal author of the language implementation.” Bruce wrote the definitive PowerShell in Action (PiA), my go-to book recommendation for anyone with scripting or development experience.

PowerShell In Action – Constrained endpoints

Constrained, delegated endpoints are getting more attention nowadays with JitJea. It turns out Bruce talked about constrained endpoints in PiA long ago.

Interactive and implicit remoting depend on a few commands. If you want to create a constrained endpoint that works with interactive or implicit remoting, you should define proxy functions for these. Rather than hard code the command names, Bruce tells us how to get them at run time, by creating a certain restricted initial session state and listing the public commands within it:

$iss = [Management.Automation.Runspaces.InitialSessionState]::CreateRestricted("RemoteServer")            
$iss.Commands | Where { $_.Visibility -eq "Public" } | Select Name

RemoteServer

How does this relate to automatic variables and modules? In both cases we can use PowerShell and the .NET Framework to find the answer at run time, rather than hard coding the answer.

Automatic variables and modules

If we want to pass variables from the user’s session into the Invoke-Parallel runspaces, we probably want to ignore the wide array of automatic variables.

We could certainly hard code these, but what fun is that? The approach we ended up taking was to compare a clean environment with the current environment, by creating a clean PowerShell runspace, listing out the modules, pssnapins, and variables within it, and comparing these with the user’s current session.

$StandardUserEnv = [powershell]::Create().addscript({            
            
    #Get modules and snapins in this clean runspace            
    $Modules = Get-Module | Select -ExpandProperty Name            
    $Snapins = Get-PSSnapin | Select -ExpandProperty Name            
            
    #Get variables in this clean runspace            
    #Called last to get vars like $? into session            
    $Variables = Get-Variable | Select -ExpandProperty Name            
                
    #Return a hashtable where we can access each.            
    @{            
        Variables = $Variables            
        Modules = $Modules            
        Snapins = $Snapins            
    }            
}).invoke()[0]

AutoVariables

This isn’t perfect; certain automatic variables won’t be created out of the gate. For example, $Matches won’t be listed. But this gives us a good start, and filters out the majority of variables and modules that we can ignore. The end result? A more usable Invoke-Parallel:

$Path = "C:\Temp"            
            
#Query 3 computers for something, save the results under $path 
echo Server1 Server2 Server3 | Invoke-Parallel -ImportVariables { 
                
    #Do something and record some output!            
    $Output = "Some Value for $_"            
            
    #Save it to the path we specified outsite the runspace            
    $FilePath = Join-Path $Path "$_-$(Get-Date -UFormat '%Y%m%d').txt"                
    Set-Content $FilePath -Value $Output -force            
            
}            
            
#List the output:            
dir $Path

Invoke-Parallel

In the past, $path would not be passed in, resulting in potential confusion and broken code.

Common parameters

What if you want a list of common parameters? Perhaps you are splatting PSBoundParameters and want to exclude common parameters, or perhaps you just want a list of common parameters to jog your memory.

You could hard code these, but this is no fun, and might get complicated if you want to cover version specific parameters like PipelineVariable. Let’s use one of the core commands for exploring PowerShell to get these; Get-Command.

#Define an empty function with cmdletbinding            
Function _temp { [cmdletbinding()] param() }            
            
#Get parameters, only common params are returned            
(Get-Command _temp | Select -ExpandProperty parameters).Keys

Common-Parameters

That’s it! We simply build a temporary function, and ask PowerShell what that function’s parameters are.

Use PowerShell to explore PowerShell

The key takeaway here is that you can use PowerShell or the .NET Framework itself to explore PowerShell. Use this to your advantage when writing functions where details on the runtime environment can improve the end user’s experience.

Cheers!

Advertisements