PowerShell for Windows Admins


February 21, 2012  4:14 PM

System Center engineering blogs

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

The System Center family of products is getting a big make over in the 2012 release. The team blogs have just been rebranded to reflect this

System Center: Service Manager

System Center: Operations Manager

System Center: Virtual Machine Manager

System Center: Orchestrator

System Center: Data Protection Manager

February 21, 2012  3:58 PM

BITS

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

As well as looking at WMI I thought it was time to branch out a bit and look at other functionality that could administrators

The BITS (Background Intelligent Transfer Service) service is used for file transfer. Its normally thought of as being used across the internet but it can also be used in the enterprise. PowerShell v2 on Windows Vista/2008 and above comes with a BITS module

Import-Module BitsTransfer

which contains these cmdlets

Add-BitsFile – Adds one or more files to an existing Background Intelligent Transfer Service (BITS) transfer job.
Complete-BitsTransfer – Completes a Background Intelligent Transfer Service (BITS) transfer job.
Get-BitsTransfer – Retrieves the associated BitsJob object for an existing Background Intelligent Transfer Service (BITS) transfer job
Remove-BitsTransfer – Cancels a Background Intelligent Transfer Service (BITS) transfer job.
Resume-BitsTransfer – Resumes a Background Intelligent Transfer Service (BITS) transfer job.
Set-BitsTransfer – Modifies the properties of an existing Background Intelligent Transfer Service (BITS) transfer job.
Start-BitsTransfer – Creates a new Background Intelligent Transfer Service (BITS) transfer job.
Suspend-BitsTransfer – Suspends a Background Intelligent Transfer Service (BITS) transfer job.

The actual transfer can be accomplished like this

Start-BitsTransfer -Source http://webr201/transfer/test1.txt -Destination c:\source\transfer\

The help states that wildcards are supported in the source but my testing, so far just produces an error message.

So far I’ve been using a full IIS server as the source – there is another way as we will see later


February 21, 2012  2:24 PM

Win32_AutocheckSetting class

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

I stumbled on this class

Get-WmiObject Win32_AutochkSetting

The useful output is

Caption          :
Description      :
SettingID        : Microsoft Windows 7 Ultimate |C:\Windows|\Device\Harddisk0\Partition2
UserInputDelay   : 10

 

Be careful with the documentation because it states that SettingID holds the date and time of installation! 

I’ve made the output a bit easier to understand

$acs = Get-WmiObject -Class Win32_AutochkSetting             
$setting = $acs.SettingID -split "\|"            
New-Object -TypeName PSObject -Property @{            
  OS = $setting[0]            
  Path = $setting[1]            
  Disk = $setting[2]            
  'Delay(s)' = $acs.UserInputDelay            
}


February 20, 2012  4:35 PM

UG meeting reminder

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

First reminder for the UG meeting on 28February – PowerShell and SQL Server

details from

http://msmvps.com/blogs/richardsiddaway/archive/2012/02/09/february-powershell-group-meeting-sql-server-and-powershell.aspx


February 18, 2012  2:05 PM

Count of files in a folder

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

I was recently left a comment of a post

http://richardspowershellblog.wordpress.com/2009/11/30/updating-access-data/

asking about the how to get the count of files in a folder

There are a number of solutions including dropping back to the FileSystem object from VBscript

If we want just a PowerShell option

function filecount {            
param (            
 [string]$path            
)            
 if (-not (Test-Path $path)){Throw "Path: $path not found"}            
             
 $count = 0            
 $count = Get-ChildItem -Path $path |             
          where {!$_.PSIsContainer} |             
          Measure-Object |            
          select -ExpandProperty count            
                      
 Get-Item -Path $path |             
 select PSDrive,             
 @{N="Parent"; E={($_.PSParentPath -split "FileSystem::")[1]}},            
 Name,            
 @{N="FileCount"; E={$count}}            
             
 Get-ChildItem -Path $path |             
 where {$_.PSIsContainer} |            
 foreach {            
   filecount $($_.Fullname)            
 }            
            
}             
            
filecount "c:\scripts"

Supply a path to the top level folder for the filecount function

It will test if the folder exists & complain if it doesn’t.

We can then count the files in the folder using PSISContainer to filter out any subfolders and measure-object to perform the count.

Get-item is used on the path and piped into select where we split out the parent path and add the file count (that count be done with add-member as well

Get-ChildItem is used on the path and only folders are passed. The path of each subfolder is passed to the filecount function. 

A function calling itself like this known as recursion


February 18, 2012  4:58 AM

LDAP filter issues

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

I have been using LDAP filters a lot recently. One thing that can cause subtle errors that are difficult to track down are LDAP filters.

As an example consider this code which discovers all objects with the creatorSID attribute set and then resolves that SID to discover the user who created the object. The creatorSID attribute is not set if  a domain admin creates the object

function Resolve-SIDtoUser {                        
 param ([byte[]]$sid)                        
  $sb = New-Object -TypeName System.Text.StringBuilder                        
                          
  for ($i=0; $i -lt $sid.Length; $i++){                        
   $sb.AppendFormat("\{0}",  $sid[$i].ToString("X2")) | Out-Null                        
  }                        
  $strsid = $sb.ToString()                        
                            
  $root = [ADSI]""                        
  $search = [adsisearcher]$root                        
  $search.Filter = "(objectSID=$strsid)"                        
  $search.FindOne() | foreach {                        
    return $_.Properties.name                        
  }                         
}                        
                        
$dom = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()                        
$domain = $dom.GetDirectoryEntry()                        
$dn = $($domain.distinguishedname)                         
                        
                        
"`nScript"                        
$root = [ADSI]""                        
$search = [adsisearcher]$root                        
$search.Filter = "(mS-DS-CreatorSID=*)"                         
$search.SizeLimit = 3000                        
$search.FindAll() | foreach {                        
                         
 $obj = $_.GetDirectoryEntry()                        
                         
 $user = Resolve-SIDtoUser $obj.Properties."mS-DS-CreatorSID".value                        
                          
 $obj | select  @{N="ObjectClass"; E={$_.SchemaClassName}},                         
 @{N="Name"; E={$_.name}},                         
 @{N="DistinguishedName"; E={$_.distinguishedname}},                        
 @{N="Creator"; E={$user}}                        
 } | Format-Table -AutoSize

In this we have to LDAP filters. The one at the bottom

$search.Filter = "(mS-DS-CreatorSID=*)" 

fires first and checks for all objects that have the ms-DS-CreatorSID attribute set.

What would happen if this was changed to

$search.Filter = "(mS-DS-CreatorSID=* )"

Looks the same at first glance but actually you would get nothing back! In the original version * indicates that the attribute has a value. You are dealing with strings in the filter to the change looks for ‘* ‘ i.e. something followed by a space!

However

$search.Filter = "(mS-DS-CreatorSID= *)"

does work because the * is still by itself.

This also is an issue if you are using variable substitution to create the filter. Consider the filter in the resolve-sidtouser function

$search.Filter = "(objectSID=$strsid)" 

On the face of it changing this to

$search.Filter = "(objectSID=$strsid )" 

shouldn’t matter but its the same case as above the space counts.

These rules also apply if you are using an LDAP filter with the Microsoft cmdlets, Quest cmdlets or the provider

The bottom line with LDAP filters be careful of spaces and don’t leave any after the filter definition.


February 15, 2012  12:23 PM

My first script

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

PowerShell is an incredible tool with the ability to automate a load of your mundane tasks. A frequent question is where do I start?

Now the obvious starting point is to learn a little PowerShell – PowerShell in a Month of Lunches is ideal if you are a complete beginner otherwise jump straight to PowerShell in Practice. Both are available from www.manning.com and all good book shops.

The next thing is picking the tasks to automate. What is causing you grief? What is being done inconsistently in your organisation? What takes up a lot of time and effort?

A few things come to mind:

  • creating AD user accounts and mailboxes – easy enough to create 1 in the GUI but it quickly gets tedious if you need to do many of them.  Also  the user names can be standardised. When I audit AD environments I see lots of examples where the names aren’t consistent – firstname lastname; lastname firstname;  lastname,firstname.  I really hate the last one – using a comma causes problems when creating/using distinguished names – don’t do it!
  • removing expired AD accounts – its easy to test when a user last logged on – set a date and disable accounts that haven’t been used
  • server configuration – most organisations have a requirement to create configuration documentation. Most admins don’t do it because its boring. Automate it with a set of WMI calls and you have instant documentation.  I recently refreshed the documentation for a couple hundred servers over a few nights
  • reporting e.g. disk usage, mailbox sizes, allocation of CPU and memory on virtual machines to test over commitment

 

Pick something that will be of immediate benefit to automate and do it. You may not get it all first pass but even taking away some of the manual work will free up a bit more time. The more you can speed up the mundane tasks the more time you have to devote to the interesting stuff


February 13, 2012  12:16 PM

String replacement

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

A question of the forum involved changing the file path for a folder. The original path was like this

$folder = "C:\FolderName\NextFolder\ThirdFolder"

 

The first attempt uses the –replace operator

PS> $newfolder = $folder -replace "C:\FolderName\", "D:\"
Invalid regular expression pattern: C:\FolderName\.
At line:1 char:30
+ $newfolder = $folder -replace <<<<  "C:\FolderName\", "D:\"
    + CategoryInfo          : InvalidOperation: (C:\FolderName\:String) [], RuntimeException
    + FullyQualifiedErrorId : InvalidRegularExpression

 

The way to get round this is to escape the \ with another \ to keep the regular expressions happy

PS> $newfolder = $folder -replace "C:\\FolderName\\", "D:\"
PS> $newfolder
D:\NextFolder\ThirdFolder

 

Alternatively if you don’t care about happy regex then use the Replace() method of the string class

PS> $newfolder = $folder.Replace("C:\FolderName\", "D:\")
PS> $newfolder
D:\NextFolder\ThirdFolder

 

Its simpler than dealing with regex and escaping characters to meet its quirks


February 12, 2012  4:31 AM

Domain Controller Service Health–revisited

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

A bit more digging as a follow up to the previous post shows that the NTDS service is shown when PowerShell is run with elevated privileges i.e. Run as Administrator. That means we want to be able to test is PowerShell is running in that mode

The test-dcservicehealth function becomes

function test-dcServiceHealth {            
[CmdletBinding()]            
param (            
 [parameter(Position=0,            
   ValueFromPipeline=$true,             
   ValueFromPipelineByPropertyName=$true)]            
 [string]$computername=$env:COMPUTERNAME            
)            
            
PROCESS {            
$currentUser = [Security.Principal.WindowsIdentity]::GetCurrent()            
if (! (New-Object Security.Principal.WindowsPrincipal $currentUser).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)){            
  Write-Warning "Must be run as administrator"            
  return            
}            
            
"ADWS", "Dfs",  "DFSR", "DNS", "IsmServ", "kdc", "Netlogon", "NTDS", "NtFrs", "W32Time", "WinRM" |            
foreach {            
            
 Get-WmiObject -Class Win32_Service -Filter "Name = '$($_)'"-ComputerName $computername |            
 select Name, DisplayName, State,            
 @{N="DC";E={$computername}}            
}            
}            
}

I’ve added advanced function parameter attributes so the function accepts pipeline input, added NTDS to the list of services and added a test to see if PowerShell is running as administrator – if it isn’t it returns with a warning

 

which means we can do this

"dc02", "server02" | test-dcServiceHealth | ft -GroupBy DC –AutoSize

 

It would be better to get the domain controllers automatically so

function get-DomainControllerNames {            
 $dom = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()            
 $dom.FindAllDomainControllers() | select -ExpandProperty Name            
}

which then means we do

get-DomainControllerNames | test-dcServiceHealth | ft -GroupBy DC –AutoSize

and we get a nicely formatted report


February 11, 2012  2:36 PM

Domain Controller Service Health

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

The correct functioning of our AD domains depends on the services that are running on our domain controllers

A quick test for the services that could affect AD

function test-dcServiceHealth {            
[CmdletBinding()]            
param (            
 [string]$computername=$env:COMPUTERNAME            
)            
"ADWS", "Dfs",  "DFSR", "DNS", "IsmServ", "kdc", "Netlogon", "NtFrs", "W32Time", "WinRM" |            
foreach {            
            
Get-WmiObject -Class Win32_Service -Filter "Name = '$($_)'"-ComputerName $computername |            
select Name, DisplayName, State,            
@{N="DC";E={$computername}}            
}            
}

One service that is missing is the AD Domain Service for Windows 2008 R2.  That doesn’t seem to show through WMI or Get-Service. I’m still investigating though if DNS and kdc are running they are both dependent on NTDS so it has to be running


Forgot Password

No problem! Submit your e-mail address below. We'll send you an e-mail containing your password.

Your password has been sent to: