Programming:Windows PowerShell

From Wiki
Jump to: navigation, search


Best practices

Stub entry. See for now.

Limit the number of results

Here is an example of querying the Services list and limiting the results to 5 items:

Get-Service | Select-Object -First 5


Status   Name               DisplayName
------   ----               -----------
Stopped  AeLookupSvc        Application Experience
Running  AGSService         Adobe Genuine Software Integrity Se...
Stopped  ALG                Application Layer Gateway Service
Stopped  AppIDSvc           Application Identity
Running  Appinfo            Application Information

Leave out column headers in output

Here is an example of querying the Services list, limiting the results to 10 items and then sorting them, all as as stream of values:

Get-Service | Select-Object -First 10 -ExpandProperty DisplayName | sort

This gives a bare list like so:

ActiveX Installer (AxInstSV)
Adobe Acrobat Update Service
Application Experience
Application Identity
Application Information
Application Layer Gateway Service
Application Management
ASP.NET State Service
Windows Audio
Windows Audio Endpoint Builder

The -ExpandProperty parameter takes the value of an incoming object, enumerates its values and outputs each of those values as a single record on the output stream. By pointing it at a scalar property (a property that is not a collection), you get the raw value and not an object with a single property.

Run a single PowerShell command from a batch script

powershell -Command "& {echo $PSVersionTable}"

Not the most useful example, but it illustrates running a quick local command.


List commands which accept specific parameter

For example, here are the commands provided by PowerShell v3.0 on a Windows 7 Professional system that accept the ComputerName parameter:

Get-Command -ParameterName ComputerName
CommandType     Name                                               ModuleName
-----------     ----                                               ----------
Cmdlet          Connect-PSSession                                  Microsoft.PowerShell.Core
Cmdlet          Enter-PSSession                                    Microsoft.PowerShell.Core
Cmdlet          Get-PSSession                                      Microsoft.PowerShell.Core
Cmdlet          Invoke-Command                                     Microsoft.PowerShell.Core
Cmdlet          New-PSSession                                      Microsoft.PowerShell.Core
Cmdlet          Receive-Job                                        Microsoft.PowerShell.Core
Cmdlet          Receive-PSSession                                  Microsoft.PowerShell.Core
Cmdlet          Remove-PSSession                                   Microsoft.PowerShell.Core
Cmdlet          Send-MailMessage                                   Microsoft.PowerShell.Utility

String Matching

One of the early points that was (repeatedly) emphasized to me is that PowerShell is a shell and as such has access to the same applications that the standard Command Prompt does. The author/instructor's point was that I (the reader, student) shouldn't put off using PowerShell until we got good enough to "switch over" from the standard shell, we should go ahead and use it now for what we've been using the Command Prompt for and start using PowerShell's improved features as I learn more.

This is an example of what was difficult to do with regular DOS/Command Prompt, but PowerShell makes easy:


netstat -an | select-string -Pattern "8080", "8085"
  TCP               LISTENING
  TCP              LISTENING

I would run @netstat@ like so on a UNIX box: netstat -na | grep -E '8080|8085'


PowerShell v2, ForEach loop statement with null/empty lists

As I write this, I'm still using Windows 7 as my predominant Windows OS and I am learning PowerShell. Windows 7 comes with PowerShell v2.0 out of the box, so unless I take the time to install something newer, I can count on PowerShell v2 to be present on Windows 7 and Server 2008 R2 systems that I manage.

Unfortunately, PowerShell v2's ForEach statement has a nasty bug that will cause it to execute once when it encounters a null value. This bit me repeatedly until I found a blog post which clarified that I wasn't imagining things. The fix is to make sure that you don't give the ForEach statement an empty collection.

Here is an example of doing so based on some code I'm working with (remember, I am a newbie):

$installed_app_versions = GetInstalledAppVersions $AppName

# The 'foreach' loop construct will loop once if the array is null in PowerShell v2, so we have
# to guard against that possibility
if ($installed_app_versions -ne $null) {
    foreach ($app in $installed_app_versions)
            $app_package_guid = $app | Select-Object -ExpandProperty PSChildName
            $app_package_name = $app | Select-Object -ExpandProperty DisplayName

            # Using Write-Output to add "blocking" characteristic to the removal command
            # so that PowerShell waits for it to complete before running the next
            echo "`n[i] Uninstalling $app_package_name ..."
            RemoveAppVersions $app_package_guid


This seems to work well and appears to be best practice. Thankfully this behavior is fixed with PowerShell v3 and later.

Run PowerShell script under 64-bit process even when parent process is 32-bit


  1. Batch script is executed from a 32-bit process and does some prep work
  2. Same script needs to run a PowerShell script which will need to see both the 32-bit and 64-bit view of the registry

You will get a 32-bit version of PowerShell from a 32-bit process (by default)

If the batch script does any of the following the result will be a redirection to the 32-bit PowerShell executable:

  1. powershell.exe -executionpolicy bypass -File SCRIPT_NAME.ps1 -Parameter1 "Value1" -Parameter2 "Value2"
    • see section below for why we're bypassing the execution policy
  2. %windir%\System32\WindowsPowerShell\v1.0\powershell.exe -executionpolicy bypass -File SCRIPT_NAME.ps1 -Parameter1 "Value1" -Parameter2 "Value2"
    • System32 is the directory for 64-bit executables (yeah, I know the name implies otherwise)
  3. %windir%\SysWOW64\WindowsPowerShell\v1.0\powershell.exe -executionpolicy bypass -File SCRIPT_NAME.ps1 -Parameter1 "Value1" -Parameter2 "Value2"
    • SysWOW64 is the directory for 32-bit executables

Option 1 lets the OS resolve the path to powershell.exe. If you call powershell.exe from a 64-bit process (native cmd.exe on a x64 box for example), you get a 64-bit version of PowerShell. If you call it from a 32-bit process, you get a 32-bit version of PowerShell. Option 3 is just giving up an explicit request for a 32-bit version of PowerShell.

Getting a 64-bit version of PowerShell

Thankfully Windows provides a way to bypass the automatic "helpful" redirection of requests for 64-bit executables to 32-bit executables. To do this, reference the %windir%\Sysnative folder from a 32-bit process. A 64-bit process (AFAIK) is not provided access to this virtual folder.

Here is an example of determining the path to a 64-bit version of PowerShell from a 32-bit command-prompt:

set POWERSHELL_EXE=%windir%\system32\WindowsPowerShell\v1.0\powershell.exe

:: Using the 'Sysnative' folder will help you access 64-bit tools from 32-bit code
:: Note: the 'Sysnative' folder is ONLY available on a x64 Windows Vista or newer
IF /i EXIST "%ProgramFiles(x86)%" set POWERSHELL_EXE=%windir%\sysnative\WindowsPowerShell\v1.0\powershell.exe

Now when you use %POWERSHELL_EXE% in the batch script (32-bit command-prompt) the 64-bit version of PowerShell is used and any PowerShell scripts which need access to both 32-bit and 64-bit views of the registry should have it.

How can I tell if my PowerShell session is 32-bit or 64-bit?

Here is the command/output for a 32-bit PowerShell session:

PS C:\Windows> Get-Variable PSHome

Name                           Value
----                           -----
PSHOME                         C:\Windows\SysWOW64\WindowsPowerShell\v1.0

and the command/output for a 64-bit PowerShell session:

PS C:\Windows> Get-Variable PSHome

Name                           Value
----                           -----
PSHOME                         C:\Windows\System32\WindowsPowerShell\v1.0

Determining the version of PowerShell our script is run under

From a Windows 7x64 system:

PS C:\> $PSVersionTable

Name                           Value
----                           -----
CLRVersion                     2.0.50727.548
BuildVersion                   6.1.7601.1751
PSVersion                      2.0
WSManStackVersion              2.0
PSCompatibleVersions           {1.0, 2.0}
PSRemotingProtocolVersion      2.1

PS C:\> $PSVersionTable.PSVersion

Major  Minor  Build  Revision
-----  -----  -----  --------
2      0      -1     -1

PS C:\> $PSVersionTable.PSVersion.ToString()

From a Windows 10 system:

PS C:\> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      5.0.10240.16384
WSManStackVersion              3.0
CLRVersion                     4.0.30319.42000
BuildVersion                   10.0.10240.16384
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
PSRemotingProtocolVersion      2.3

PS C:\> $PSVersionTable.PSVersion

Major  Minor  Build  Revision
-----  -----  -----  --------
5      0      10240  16384

PS C:\> $PSVersionTable.PSVersion.ToString()

Limiting the version of PowerShell our script runs under

PowerShell allows limiting the version it supports by using the -Version VERSION_NUMBER parameter when you start it. By specifying the version number here you limit the highest version of PowerShell that should be supported. This can be useful for testing scripts that have to execute in an environment (outside of your control) where you're not able to install the latest version of PowerShell.


PowerShell -Version <Powershell-Version> -Command {<Scriptblock>}

Requiring a specific version of PowerShell

Here we require at least PowerShell version 2.0:

#Requires -Version 2.0

The same can be done for 3.0, 4.0 and 5.0 (latest as of this writing). To test, you can set the version past the currently available versions of PowerShell:

#Requires -Version 6.0

Note: The lack of a space between the number sign and Requires -Version X.Y is intentional. Adding a space makes the line just another comment.

Event Log

Get-EventLog -LogName Application -Source MSSQLSERVER -Newest 20 -Message *Autogrow* | 
Select-Object -Property TimeGenerated, Message | 

This is useful for grabbing messages related to timeouts related to Autogrowing transaction logs for Microsoft SQL Server. We're choosing the list format in order to prevent truncation of the log message.


Execution Policy

PowerShell x86 and x64 have different policies

This had me scratching my head until trial/error got me past it. It wasn't until later that I stumbled across a blog post which clarified that the x86 and x64 versions of PowerShell each have different execution policies. Setting the policy for one does not set it for the other.

These are the registry values where the chosen policy settings are stored:

  • x64: HKLM\Software\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell\ExecutionPolicy
  • x86: HKLM\Software\Wow6432Node\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell\ExecutionPolicy

I expect the paths are subject to change for a future PowerShell edition.

Setting policy for both x64 and x86 versions of PowerShell

Using PowerShell

Tested on a x64 Windows 7 OS, but presumably this works the same up to Windows 10.

  1. Run x86 version of PowerShell with elevated rights
    • "Run as Administrator" with UAC enabled
    • "Run as different user" with UAC disabled
  2. Run Get-ExecutionPolicy to examine the current policy
  3. Run Set-ExecutionPolicy VALUE to set VALUE as the policy
    • Current valid values:
      • Unrestricted
      • RemoteSigned
      • AllSigned
      • Restricted
      • Default
      • Bypass
      • Undefined
  4. Run x64 version of PowerShell with elevated rights
  5. Repeat the steps for the x86 version of PowerShell
Using reg.exe

Example of setting the execution policy for 64-bit systems (remotely) using the reg.exe command-line tool:

for /f %i in (\\utilityserver\servers.txt) do reg add \\%i\HKLM\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell /v ExecutionPolicy /t REG_SZ /d VALUE /f

Setting the value locally is handled roughly the same way:

reg add HKLM\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell /v ExecutionPolicy /t REG_SZ /d VALUE /f

Bypassing the PowerShell Execution Policy


  1. Batch script runs which does some prep work
  2. Same script runs a PowerShell script to do some heavy lifting
  3. PowerShell script completes and batch script performs cleanup work

I need this process to work everywhere the batch script runs and I don't know in advance what the Execution Policy will be. Sure, I could add registry settings before calling the script, but I want to just bypass the policy for a short time so my PowerShell script can be executed properly. Here is what appears to work just fine on a Windows 7x64 system:

powershell.exe -executionpolicy bypass -File SCRIPT_NAME.ps1 -Parameter1 "Value1" -Parameter2 "Value2"

SQL Server

List enabled SQL Server Agent jobs

# Load required assembly
[System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SMO') | out-null

# Create new object/connection
$sqlsvr = New-Object ('Microsoft.SqlServer.Management.Smo.Server') "LOCALHOST"

# Examine object
$sqlsvr.JobServer.Jobs | Where-Object {$_.IsEnabled -eq $FALSE} | Select Name,LastRunOutcome,LastRunDate

Active Directory

Look up Active Directory group members

I found this tip on

Import-Module ActiveDirectory
Get-ADGroupMember "MyADGroupName" | Select-Object name | Sort-Object name

If you wish to expand all group members from any included groups, run the command like so:

Import-Module ActiveDirectory
Get-ADGroupMember "MyADGroupName" -recursive | Select-Object name | Sort-Object name

and if you wish to instead list by Distinguished Name use this:

Import-Module ActiveDirectory
Get-ADGroupMember "MyADGroupName" -recursive | Select-Object distinguishedName | Sort-Object distinguishedName

An alternate approach is to use dsquery and dsget from the Remote Server Administration Tools (RSAT) package:

dsquery group -name "MyADGroupName" | dsget group -members -expand

With the current set of options the list of users will be by Distinguished Name.

Look up group memberships for user account

Import-Module ActiveDirectory
Get-ADPrincipalGroupMembership username | select name
Domain Users
Domain Computers
Workstation Admins
Company Users
Company Developers

Copy all users in one group to another group

AFAIK, both groups have to be created in advance.

Import-Module ActiveDirectory
Get-ADGroupMember -Identity GROUP-A | Add-ADPrincipalGroupMembership -MemberOf GROUP-B


Retrieve SerialNumber, BIOS Version, etc

These are more examples of calling PowerShell via a batch script or via a command-line. This approach might be useful if you need to request BIOS details from a person who isn't used to running PowerShell directly or via some sort of scripted process.


Get-WmiObject win32_bios


Get-WmiObject win32_bios -Computer REMOTE_COMPUTER_NAME

Retrieve number (and size) of memory modules installed

Useful if you want to know how many modules are physically installed and what size without having to open the case.

Get-WmiObject Win32_MemoryDevice | Select-Object -Property DeviceID, @{Name="Size";Expression={ "{0:N0}" -f  ($($_.EndingAddress - $_.StartingAddress) / 1024) }}
DeviceID        Size
--------        ----
Memory Device 0 2,048
Memory Device 1 2,048

Here we see that the laptop I'm using has 2x2GB memory modules installed. Admittedly, it's far easier to use wmic to retrieve the info:

wmic memorychip get BankLabel,DeviceLocator,Capacity,Tag
BankLabel  Capacity    DeviceLocator   Tag
BANK 0     2147483648  ChannelA-DIMM0  Physical Memory 0
BANK 2     2147483648  ChannelB-DIMM0  Physical Memory 1