Programming:Windows PowerShell

From WhyAskWhy.org Wiki
Revision as of 10:58, 21 June 2016 by Deoren (talk | contribs) (Added Compatibility section with note re PowerShell v2 and the ForEach loop statement behavior with empty/null lists/collections.)

Jump to: navigation, search


General

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.

Compatibility

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

# SANITY CHECK
#
# 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.

Security

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

Goals:

  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 ServerFault.com.

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
name
----
Domain Users
Domain Computers
Workstation Admins
Company Users
Company Developers
AutomatedProcessingTeam

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


Links