Binding Parameters by Data Type

PowerShell can automatically bind values to parameters by data type matching. Here is a sample that shows what benefit this can be. Simply run this function:

function Test-Binding
{
    [CmdletBinding(DefaultParameterSetName='Date')]
    param
    (
        [Parameter(ParameterSetName='Integer', Position=0, Mandatory=$true)]
        [int]
        $Id,
        
        [Parameter(ParameterSetName='String', Position=0, Mandatory=$true)]
        [string]
        $Name,
        
        [Parameter(ParameterSetName='Date', Position=0, Mandatory=$true)]
        [datetime]
        $Date
    )
    
    $chosenParameterSet = $PSCmdlet.ParameterSetName
    Switch ($chosenParameterSet)
    {
        'Integer' { 'User has chosen Integer' } 
        'String'  { 'User has chosen String' } 
        'Date'    { 'User has chosen Date' } 
    }
       
    [PSCustomObject]@{
        Integer = $Id
        String = $Name
        Date = $Date
    }
}

Now a user can call Test-Binding and submit arguments:

 
PS C:> Test-Binding "Hello"
User has chosen String

Integer String Date
------- ------ ----
      0 Hello      



PS C:> Test-Binding 12
User has chosen Integer

Integer String Date
------- ------ ----
     12            



PS C:> Test-Binding (Get-Date)
User has chosen Date

Integer String Date               
------- ------ ----               
      0        11/21/2017 11:44:33 AM
 

Twitter This Tip! ReTweet this Tip!

Deleting Environment Variables

In the previous tip we explained how you can set environment variables in all available scopes. But how would you remove environment variable?

Coincidentally, you can do that the same way, simply by providing an empty string as value. However, the function from our previous tip will not accept empty strings for the -VariableValue parameter:

function Set-EnvironmentVariable
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory)][String]
        $VariableName,
        
        [Parameter(Mandatory)][String]
        $VariableValue,
        
        [Parameter(Mandatory)][EnvironmentVariableTarget]
        $Target        
    )
    
   [Environment]::SetEnvironmentVariable($VariableName, $VariableValue, $Target)
}

When you try it, this is what you will get:

 
PS C:>  Set-EnvironmentVariable -VariableName test -VariableValue "" -Target  User
Set-EnvironmentVariable  : Cannot bind Argument to Parameter "VariableValue" because it is an  empty string.
...
 

This happens because whenever you declare a parameter as “Mandatory”, PowerShell refuses empty and null values by default.

You could make “VariableValue” optional but then PowerShell would no longer prompt for it when you run the function without arguments. How do you tell a mandatory parameter to accept null and empty string values?

By adding the attributes [AllowNull()] and/or [AllowEmptyString()]! With just a little effort, the function above can be turned into one that can also delete environment variables:

function Set-EnvironmentVariable
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory)][String]
        $VariableName,
        
        [Parameter(Mandatory)][String]
        [AllowEmptyString()]
        $VariableValue,
        
        [Parameter(Mandatory)][EnvironmentVariableTarget]
        $Target        
    )
    
    [Environment]::SetEnvironmentVariable($VariableName, $VariableValue, $Target)
}

And here is the call to delete “Test” environment variable:

 
PS C:> Set-EnvironmentVariable -VariableName test -VariableValue "" -Target User
 

Twitter This Tip! ReTweet this Tip!

Managing User Profiles

To dump the raw list of user profiles on a machine, use the following line:

Get-CimInstance -Class Win32_UserProfile | Out-GridView

You will get detailed information about all user profiles. The user name can be found in the property SID, however it is in SID format. To get the real user name, the SID would need to be translated. This chunk of code produces a hash table that uses the real user names as key:

$userProfiles = Get-CimInstance -Class Win32_UserProfile |
  # add property "UserName" that translates SID to username
  Add-Member -MemberType ScriptProperty -Name UserName -Value { 
    ([Security.Principal.SecurityIdentifier]$this.SID).Translate([Security.Principal.NTAccount]).Value
  } -PassThru |
  # create a hash table that uses "Username" as key
  Group-Object -Property UserName -AsHashTable -AsString

You can now easily dump the list of users that have a user profile on that machine:

 
PS C:> $userProfiles.Keys | Sort-Object
MYDOMAINAdministrator
MYDOMAINUser01
MYDOMAINUser02
MYDOMAINUser03
MYDOMAINUser12
NT AUTHORITYLOCAL SERVICE
NT AUTHORITYNETWORK SERVICE
NT AUTHORITYSYSTEM
PC10User
 

To get detailed info on a particular user profile, access the entry in the hash table:

 
PS C:> $userProfiles["MYDOMAINUser01"]


UserName                         : MYDOMAINUser01
AppDataRoaming                   : Win32_FolderRedirectionHealth
Contacts                         : Win32_FolderRedirectionHealth
Desktop                          : Win32_FolderRedirectionHealth
Documents                        : Win32_FolderRedirectionHealth
Downloads                        : Win32_FolderRedirectionHealth
Favorites                        : Win32_FolderRedirectionHealth
HealthStatus                     : 3
LastAttemptedProfileDownloadTime : 
LastAttemptedProfileUploadTime   : 
LastBackgroundRegistryUploadTime : 
LastDownloadTime                 : 
LastUploadTime                   : 
LastUseTime                      : 
Links                            : Win32_FolderRedirectionHealth
Loaded                           : False
LocalPath                        : C:UsersUser01
Music                            : Win32_FolderRedirectionHealth
Pictures                         : Win32_FolderRedirectionHealth
RefCount                         : 
RoamingConfigured                : False
RoamingPath                      : 
RoamingPreference                : 
SavedGames                       : Win32_FolderRedirectionHealth
Searches                         : Win32_FolderRedirectionHealth
SID                              : S-1-5-21-3860347202-3037956370-3782488958-1604
Special                          : False
StartMenu                        : Win32_FolderRedirectionHealth
Status                           : 0
Videos                           : Win32_FolderRedirectionHealth
PSComputerName                   : 
 

Twitter This Tip! ReTweet this Tip!

Running Commands on Multiple Computers in Parallel

Provided you have enabled PowerShell remoting (see our previous tips), you can easily run commands and scripts on many computers at the same time.

The example below illustrates this, and places a text file on the desktop of all users on the listed computers. Warning: this is a very powerful script. It executes anything you place in $code on all listed machines, provided remoting is enabled, and you have proper permissions:

# list of computers to connect to
$listOfComputers = 'PC10','TRAIN1','TRAIN2','AD001'
# exclude your own computer
$listOfComputers = $listOfComputers -ne $env:COMPUTERNAME
# code to execute remotely
$code = {
"Hello" | Out-File -FilePath "c:usersPublicDesktopresult.txt"
}
# invoke code on all machines
Invoke-Command -ScriptBlock $code -ComputerName $listOfComputers -Throttle 1000

If you replace the code in $code with “Stop-Computer –Force”, for example, all machines would shut down.

Twitter This Tip! ReTweet this Tip!

Accessing Remote Machines via PowerShell Remoting

Once you have enabled PowerShell remoting on a target machine, try and connect to it interactively. Here is a line you should try. Just make sure you replace “targetComputerName” with the name of the target computer you want to connect to:

 
PS C:> Enter-PSSession -ComputerName targetComputerName

[targetComputerName]: PS C:UsersUser12Documents> $env:COMPUTERNAME
TARGETCOMPUTERNAME

[targetComputerName]: PS C:UsersUser12Documents> exit

PS C:> $env:COMPUTERNAME
YOURCOMPUTERNAME
 

If connection fails with an “Access Denied”, you may want to use the –Credential parameter and log on to the remote computer using a different user account. You will need local Administrator privileges.

If your connection fails with an RDP error, or if WinRM cannot find the target computer name, check the connection with Test-WSMan, and if it cannot connect, review your remoting setup. You may have to run Enable-PSRemoting on the target machine first as described in an earlier tip.

Do not run commands that open windows. Only run commands that produce text information.

To leave the remote session, use “exit”.

Twitter This Tip! ReTweet this Tip!

Playing with PowerShell Remoting

If you’d like to test-drive PowerShell remoting, you need to enable it at least on the target machine (the one you’d like to visit). For this, you need local Administrator privileges on the target machine. Open PowerShell with Administrator privileges, and run the code below:

#requires -RunAsAdministrator

# manually enable PowerShell remoting
Enable-PSRemoting -SkipNetworkProfileCheck -Force

Next, from any other computer that has network access, run the “Ping” for PowerShell remoting to see whether you can reach the target:

# "ping" for PowerShell remoting
# tests anonymously whether you can reach the target
$IPorNameTargetComputer = 'place name or IP address here'
Test-WSMan $IPorNameTargetComputer

Test-WSMan returns text similar to the text below when the target computer responds. It does an anonymous test, so if it fails, you know there is an issue either with the firewall or the target computer configuration.

 
wsmid           : http://schemas.dmtf.org/wbem/wsman/identity/1/wsmanidentity.xsd
ProtocolVersion : http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd
ProductVendor   : Microsoft Corporation
ProductVersion  : OS: 0.0.0 SP: 0.0 Stack: 3.0 
 

In our next tips, we’ll look at what you can do with PowerShell remoting, and how to execute scripts remotely.

Twitter This Tip! ReTweet this Tip!