PowerShell is a scripting language introduced in Windows Vista that provides an object-based, robust scripting language for the Windows platform. PowerShell is the successor of batch scripting, but is far more powerful.

Posts

Updating Windows Deployment Services Images From WSUS

Sometimes you’ll run into a situation where two pieces of Microsoft software don’t play well together, or in a way that would be convenient from the user’s perspective.  Such is the case with Windows Deployment Services and Windows Server Update Services.  One piece of technology stores and deploys operating system images.  The other piece of technology updates Windows operating systems.  It seems like there ought to be some glue that brings the two together because, otherwise, you need to deploy the OS image, update it manually, then recreate it.

The bad news is, no such glue exists out of the box. The good news is, it’s not difficult to create it with PowerShell (provided WDS is running on Server 2012).  Below is my project, hosted on GitHub. It’s a single, short PowerShell script which, when run on a Server 2012 WDS server, will cycle through all of your Install Images and update them using your WSUS Repository from the WsusContent share. All you need to do is run the script from the WDS server. It will ask for a scratch location where it can extract the WIM images from Windows Deployment Services, and the location of your WSUS Repository.  I recommend running it over a weekend, because, depending on the number of images stored in WDS and the number of updates in WSUS, it can take a day or longer.

The script requires interactive input, but with just a little modification you can easily hard-code the information it needs about your environment, and run it as a scheduled task on your WDS Server.

Use a Group’s SID Rather than Group Name For ThinApp Deployments

When I deploy applications with VMWare’s ThinApp, I like to limit the users that can use the application by specifying a single group via the PermittedGroups option in the package.ini file. Then i can control deployment by adding and removing users from the group in Active Directory.

When I’ve discovered is that your applications become useless if you rename the group after you build the application package. I didn’t find this out until I started standardizing our group naming conventions and began adding a prefix to all groups that control application deployment.

Fortunately PermittedGroups also supports the use of group SIDs, which never change regardless of the name of the group. I’ll get the group’s SID using PowerShell, then paste it into the PermittedGroups line in package.ini, then rebuild.  After that I can do anything I want to the group (other than delete it) and it’s ability to assign the ThinApp will be affected.


# Return the SID of an Active Directory group
[string](Get-ADGroup “Paste Group Name here”).SID

Retrieving Recursive Group Memberships with PowerShell

This is a problem that has plagued me for a long time. I’ve seen solutions implemented in VBScript, and I’ve seen PowerShell solutions that relied on third-party tools to get the job done.  Well I’ve finally implemented a solution to finding all of an account’s group memberships with PowerShell.

The following script provides a function I call Get-ADPrincipalGroupMembershipRecursive, which I named after the built-in function ADPrincipalGroupMembership.  The function is called by passing the distinguished name of the account.  It will determine all of the group’s memberships using the memberOf attribute, then recursively check those groups, their subgroups, etc. until a comprehensive list of the account’s memberships has been built.

<# .SYNOPSIS Gets all the Active Directory groups that have a specified user, computer, group, or service account. .DESCRIPTION Gets all the Active Directory groups that have a specified user, computer, group, or service account. Unlike the built-in Get-ADPrincipalGroupMembership cmtlet, the function I've provided below will perform a recursive search that will return all of the groups that the account is a member of through membership inheritance. This function required the Active Directory module and thus must be run on a domain controller or workstation with Remote Server Administration Tools. .PARAMETER dsn The distinguished name (dsn) of the user, computer, group, or service account. .PARAMETER groups An array of ADObject instances for each group in which the user, computer, group, or service account is a member. This parameter can be ignored and in fact should never be specified by the caller. The groups parameter is used internally to track groups that have already been added to the list during recursive function calls. .NOTES Author : Brian Reich <breich@reich-consulting.net .LINK http://www.reich-consulting.net #> function Get-ADPrincipalGroupMembershipRecursive( ) { Param( [string] $dsn, [array]$groups = @() ) # Get an ADObject for the account and retrieve memberOf attribute. $obj = Get-ADObject $dsn -Properties memberOf # Iterate through each of the groups in the memberOf attribute. foreach( $groupDsn in $obj.memberOf ) { # Get an ADObject for the current group. $tmpGrp = Get-ADObject $groupDsn -Properties memberOf # Check if the group is already in $groups. if( ($groups | where { $_.DistinguishedName -eq $groupDsn }).Count -eq 0 ) { $groups += $tmpGrp # Go a little deeper by searching this group for more groups. $groups = Get-ADPrincipalGroupMembershipRecursive $groupDsn $groups } } return $groups } # Simple Example of how to use the function $username = Read-Host -Prompt "Enter a username" $groups = Get-ADPrincipalGroupMembershipRecursive (Get-ADUser $username).DistinguishedName $groups | Sort-Object -Property name | Format-Table

Check if a Program Is Installed Using PowerShell 3

When I write scripts to automate the deployment of software, I always prefer to check to see if a program is already installed rather than run the install and see if it fails.  One way to do this is to use Get-WmiObject to query the WMI_Product table. This is certainly the most direct way but it’s slow and Microsoft recommends against it, as it forces the Windows Installer service to check the validity of all installed MSI’s and can actually cause problems.

Microsoft’s solution is to query the HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall registry key to check for installed software. The code at the link I provided works, but since PowerShell provides direct access to the registry with Get-ChildItem there’s a far easier way to do it:

<# Returns true if a program with the specified display name is installed. This function will check both the regular Uninstall location as well as the "Wow6432Node" location to ensure that both 32-bit and 64-bit locations are checked for software installations. @param String $program The name of the program to check for. @return Booleam Returns true if a program matching the specified name is installed. #> function Is-Installed( $program ) { $x86 = ((Get-ChildItem "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall") | Where-Object { $_.GetValue( "DisplayName" ) -like "*$program*" } ).Length -gt 0; $x64 = ((Get-ChildItem "HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall") | Where-Object { $_.GetValue( "DisplayName" ) -like "*$program*" } ).Length -gt 0; return $x86 -or $x64; }

Update: I noticed that the original version of the code was not returning true for 64-bit software that I knew to be installed.  I updated the function to check the “Wow6432Node” in the registry as well.

Posts

Generate a Group Membership Report in HTML Format Using PowerShell

<# .SYNOPSIS Generates a report in HTML format of all a user's group memberships. .DESCRIPTION Generates a report in HTML format of all a user's group memberships. The groups are separated into categories based on the group's prefix. Groups with no prefix are Role Groups. Groups with the ACL_ prefix are access control groups. Groups with the GPO_ prefix control deployment of Group Policy. Groups with the APP_ prefix control deployment of an application. Groups with the VDI_ prefix control access to a specific virtual desktop pool. .NOTES Author : Brian Reich <breich@reich-consulting.net .LINK http://wp.me/p2A8jT-1np #> # Imports the Get-ADPrincipalGroupMembershipRecursive # See https://www.reich-consulting.net/2013/12/05/retrieving-recursive-group-memberships-powershell/ Import-Module GroupManagement\Get-ADPrincipalGroupMembershipRecursive # Ask the user to enter a username $user = Get-ADUser -Identity (Read-Host "Username") # Get the user's group memberships $groups = Get-ADPrincipalGroupMembershipRecursive -dsn $user.DistinguishedName # Split groups based on prefix $acls = $groups | Where-Object { $_.Name -like "ACL_*" } $gpos = $groups | Where-Object { $_.Name -like "GPO_*" } $apps = $groups | Where-Object { $_.Name -like "APP_*" } $vdis = $groups | Where-Object { $_.Name -like "VDI_*" } # Role groups are the groups that didn't fit the other criteria $roles = $groups | Where-Object { $_ -notin $acls -and $_ -notin $gpos -and $_ -notin $apps -and $_ -notin $vdis } <# .SYNOPSIS Returns an HTML fragment listing all of the user's Role Groups. .DESCRIPTION Returns an HTML fragment listing all of the user's Role Groups. Roles Groups are displays with the name of the group and the group's description. .PARAMETER $roles An array of Role Groups .RETURNS Returns a string containing the HTML fragment. #> function Get-RoleHtml( $roles ) { $html = "<h2>Roles</h2>" $html += "<p>Role groups represent the user's job position and " $html += "responsibilities within the organization.</p>" $html += $roles | Select-Object -Property Name,Description | Sort-Object -Property Name | ConvertTo-Html -Fragment return $html } <# .SYNOPSIS Returns an HTML fragment listing all of the user's ACL Groups. .DESCRIPTION Returns an HTML fragment listing all of the user's ACL Groups. ACL Groups are named ACL_Resource Name_Permission. The name of the group is displayed alongside the Resource it controls access to and the permission it provides. .PARAMETER $acls An array of ACL Groups .RETURNS Returns a string containing the HTML fragment. #> function Get-AclHtml( $acls ) { $html = "<h2>Access Control Groups</h2>" $html += "<p>Access Control Groups (groups whose name begins with 'ACL_') are " $html += "groups that control access rights to resources.</p>" $html += $acls | Select-Object -Property ` Name, @{Name="Resource"; Expression = {$_.Name.split("_")[1]}}, @{Name="Permission"; Expression = {$_.Name.split("_")[2]}} | Sort-Object -Property Name | ConvertTo-Html -Fragment return $html } <# .SYNOPSIS Returns an HTML fragment listing all of the user's GPO Groups. .DESCRIPTION Returns an HTML fragment listing all of the user's GPO Groups. GPO Groups are displays with the name of the group and the name of the GPO it controls. .PARAMETER $roles An array of GPO groups. .RETURNS Returns a string containing the HTML fragment. #> function Get-GpoHtml( $gpos ) { $html = "<h2>Group Policy Groups</h2>" $html += "<p>Group Policy Groups (those that start with 'GPO_') are groups " $html += "that control Group Policy assignments.</p>" $html += $gpos | Select-Object -Property ` Name, @{Name="Policy Name"; Expression = {$_.Name.split("_")[1]}} | Sort-Object -Property Name | ConvertTo-Html -Fragment return $html } <# .SYNOPSIS Returns an HTML fragment listing all of the user's App Groups. .DESCRIPTION Returns an HTML fragment listing all of the user's App Groups. App Groups are displays with the name of the group alongside the Application that it controls access to. .PARAMETER $roles An array of App Groups .RETURNS Returns a string containing the HTML fragment. #> function Get-AppHtml( $apps ) { $html = "<h2>Application Groups</h2>" $html += "<p>Application Groups (those that start with 'APP_') are groups " $html += "that control deployment of specific applications. Applications may " $html += "be deployed by Group Policy, ThinApp, or other means.</p>" $html += $apps | Select-Object -Property ` Name, @{Name="Application"; Expression = {$_.Name.split("_")[1]}} | Sort-Object -Property Name | ConvertTo-Html -Fragment return $html } <# .SYNOPSIS Returns an HTML fragment listing all of the user's VDI Groups. .DESCRIPTION Returns an HTML fragment listing all of the user's VDI Groups. VDI Groups are displays with the name of the group alongside the name of the VDI pool they control access to. .PARAMETER $roles An array of VDI Groups .RETURNS Returns a string containing the HTML fragment. #> function Get-VdiHtml( $vdis ) { $html = "<h2>VDI Groups</h2>" $html += "<p>VDI Groups (those that start with 'VDI_') are groups that " $html += "that assign users to virtual desktop pools.</p>" $html += $vdis | Select-Object -Property Name,Description | Sort-Object -Property Name | ConvertTo-Html -Fragment return $html } # Get the account name, which we'll use to name the HTML File. $username = $user.SamAccountName $file = $HOME + $username + "_group_memberships.html" #The following lines generate HTML. We're generating some pretty simple markup #which contains a stylesheet to make the whole thing look decent on-screen. $html = '<html>' $html += "<head><title>RBAC Report for $username</title>" $html += "<style>" $html += " body { width: 800px; margin: 1em auto; font-family: 'Proxima Nova Regular', 'Helvetica Neue', Calibri, 'Droid Sans', Helvetica, Arial, sans-serif; font-size: 16px; font-style: normal; font-weight: normal; } h1, h2, h3, h4, h5, h6, p { border-radius: 5px; padding: 10px 20px; } h1 { background: #333; color: #f0f0f0; } h2 { background: #666; color: #f0f0f0; } p { background: #eee; color: #333 } table th { text-align: left; background: eee }" $html += "</style>" $html += "</head>" $html += '<body>'; $html += "<h1>RBAC Report for $username</h1>" $html += "<p>This report provides a list of all $username's group memberships," $html += "including all of those inherited from other groups. Groups are " $html += "organized based on their prefix. Groups with no prefix are assumed to" $html += "be Role Groups.</p>" $html += Get-RoleHtml $roles $html += Get-AclHtml $acls $html += Get-GpoHtml $gpos $html += Get-AppHtml $apps $html += Get-VdiHtml $vdis # Output the HTML to the file $HOME\$username_group_memberships.html $html | Out-File "$file" # Open the HTML file Invoke-Expression "$file"

Get All of a User’s Group Memberships with PowerShell

<# .SYNOPSIS Gets all the Active Directory groups that have a specified user, computer, group, or service account. .DESCRIPTION Gets all the Active Directory groups that have a specified user, computer, group, or service account. Unlike the built-in Get-ADPrincipalGroupMembership cmtlet, the function I've provided below will perform a recursive search that will return all of the groups that the account is a member of through membership inheritance. This function required the Active Directory module and thus must be run on a domain controller or workstation with Remote Server Administration Tools. .PARAMETER dsn The distinguished name (dsn) of the user, computer, group, or service account. .PARAMETER groups An array of ADObject instances for each group in which the user, computer, group, or service account is a member. This parameter can be ignored and in fact should never be specified by the caller. The groups parameter is used internally to track groups that have already been added to the list during recursive function calls. .NOTES Author : Brian Reich <breich@reich-consulting.net .LINK http://www.reich-consulting.net #> function Get-ADPrincipalGroupMembershipRecursive( ) { Param( [string] $dsn, [array]$groups = @() ) # Get an ADObject for the account and retrieve memberOf attribute. $obj = Get-ADObject $dsn -Properties memberOf # Iterate through each of the groups in the memberOf attribute. foreach( $groupDsn in $obj.memberOf ) { # Get an ADObject for the current group. $tmpGrp = Get-ADObject $groupDsn -Properties memberOf # Check if the group is already in $groups. if( ($groups | where { $_.DistinguishedName -eq $groupDsn }).Count -eq 0 ) { $groups += $tmpGrp # Go a little deeper by searching this group for more groups. $groups = Get-ADPrincipalGroupMembershipRecursive $groupDsn $groups } } return $groups } # Simple Example of how to use the function $username = Read-Host -Prompt "Enter a username" $groups = Get-ADPrincipalGroupMembershipRecursive (Get-ADUser $username).DistinguishedName $groups | Sort-Object -Property name | Format-Table

Create an Unattended Deployment of Visual C++ 2010 Runtime

$cwd = Split-Path $script:MyInvocation.MyCommand.Path $location = "$cwd\Visual C++ Runtime\2010" # Create Folder Structure Write-Debug "Creating Folder Structure..." # Check if path exists if( ( Test-Path "$location" -PathType Container ) -ne $true ) { # It doesn't exist, so create it. $created = New-Item "$location" -ItemType Directory # If creation failed, we can't continue if( $created -eq $false ) { Write-Debug "Cannot create folder $location." return $created } } Set-Location "$location" # Download files $source_x86 = "http://download.microsoft.com/download/5/B/C/5BC5DBB3-652D-4DCE-B14A-475AB85EEF6E/vcredist_x86.exe" $dest_x86 = "$location\vcredist_x86.exe" $source_x64 = "http://download.microsoft.com/download/3/2/2/3224B87F-CFA0-4E70-BDA3-3DE650EFEBA5/vcredist_x64.exe" $dest_x64 = "$location\vcredist_x64.exe" Write-Debug "Downloading Redistributable Packages..." try { $wc = New-Object System.Net.WebClient # If x86 package hasn't been downloaded, do it now. if( (Test-Path $dest_x86 -PathType Leaf ) -eq $false ) { Write-Debug "Downloadloading x86 package..." $wc.DownloadFile($source_x86, $dest_x86) } else { Write-Debug "x86 already downloaded, skipping..." } # It x64 package isn't already downloaded, do it now. if( (Test-Path $dest_x64 -PathType Leaf ) -eq $false ) { Write-Debug "Downloading x64 package..." $wc.DownloadFile($source_x64, $dest_x64) } else { Write-Debug "x64 package already downloaded, skipping..." } } catch [Exception] { # Bad things happened while downloading. Write-Debug $_.Exception.Message return $_.Exception.Message } Write-Debug "Extracting x86 package..." # Extract the x86 package to it's own directory. $ext_x86 = "$location\x86" if( (Test-Path $ext_x86 -PathType Container ) -eq $false ) { Write-Debug "Extracting x86 package to $ext_x86..." $ext_x86_ec = (Start-Process -FilePath "$dest_x86" -ArgumentList " /extract:`"$ext_x86`" /q" -Wait -Passthru).ExitCode if( $ext_x86_ec -ne 0 ) { Write-Debug "Failed to extract x86 package. Error code $ext_x86_ec" return $ext_x86_ec } } else { Write-Debug "$ext_x86 already exists, skipping x86 extraction." } # Extract the x64 package to it's own directory. $ext_x64 = "$location\x64" if( (Test-Path $ext_x64 -PathType Container ) -eq $false ) { Write-Debug "Extracting x64 package to $ext_x64..." $ext_x64_ec = (Start-Process -FilePath "$dest_x64" -ArgumentList " /extract:`"$ext_x64`" /q" -Wait -Passthru).ExitCode if( $ext_x64_ec -ne 0 ) { Write-Debug "Failed to extract x64 package. Error code $ext_x64_ec" return $ext_x64_ec } } else { Write-Debug "$ext_x64 already exists, skipping x64 extraction." } Write-Debug "Creating unattended install script..." $uanttend_script = "$location\unattend.ps1" if( (Test-Path $script -PathType Leaf) -ne $true ) { # Create File New-Item $uanttend_script -ItemType file | Out-Null Write-Output "`$cwd = Split-Path `$script:MyInvocation.MyCommand.Path;`r`nStart-Process -FilePath `"`$cwd\x86\Setup.exe`" -ArgumentList `"/passive`" -Wait -Passthru;`r`nStart-Process -FilePath `"`$cwd\x64\Setup.exe`" -ArgumentList `"/passive`" -Wait -Passthru;" | Out-File $uanttend_script } else { Write-Debug "Unattended install script $uanttend_script already exists. Skipping." }

Wait for Process to Complete Using PowerShell

# Execute the program. Change the -FilePath argument as needed. $script:exitCode = (Start-Process -FilePath `"setup.exe`" -Passthru -Wait).ExitCode; # Wait for process to complete. Use the Get-Process CmdLet to determine the name # of the process you need to wait for, then change the name of that process as # needed below. $running = $true; while( $running -eq $true ) { $running = ( ( Get-Process | where ProcessName -eq "setup").Length -gt 0); Start-Sleep -s 5 }

Deleting User Profiles with PowerShell

<# Deletes user profiles for all local users. @author Brian Reich <breich@reich-consulting.net> @copyright Copyright (C) 2013 Reich Web Consulting @version 1.0 #> # Get a list of all user profiles $users = Get-WmiObject Win32_UserProfile; # If you have any profiles you want to explicitly ignore, add them here. $skip = @( ); Write-Host "Cleaning up user profiles..." -ForegroundColor Magenta foreach( $user in $users ) { # Normalize profile name. $userPath = (Split-Path $user.LocalPath -Leaf).ToLower() # If the profile name was found in the skip list, don't process it. if( $skip -contains $userPath ) { Write-Host "Skipping $userPath from ignore list."; continue; } # You can't delete the profile of the currently logged-in user, so skip it. if( $userPath -eq $env:username ) { Write-Host "Skipping $userPath because it belongs to the current user."; continue; } # If the profile belongs to a "special" account (network/system), skip it. if( $user.Special -eq $true ) { Write-Host "Skipping $userPath because it is a special system account."; continue; } # If we got this far it's safe to delete. Write-Host "Deleting profile for $userPath..."; $user.Delete(); }