I’ve been doing some reflection on my PowerShell scripting skills, let me explain. When I started scripting KIXs was the norm, VBS followed quickly. One of the things I struggled with while learning PowerShell was to think in Objects. I got that one down, the pipeline… well that’s another story…
Jeffery Hicks along with other MVPs did a PowerShell Blogging Week which was great! One takeaway from that week was “Don’t Learn PowerShell, Use it!” The scripts that the MVPs made all looked and felt like cmdlets and took advantage of the pipeline!
So I reviewed my most frequently used scripts and sure enough no pipeline support! Sure the scripts do what they’re suppose to do, and in some cases they’re a bit overengineerd… Ok a lot!!!
I’m about to shame myself to make a point… Here goes…
Have a look at this:
<# .SYNOPSIS Enumerate Groups in CSV file .DESCRIPTION .NOTES Author: .LINK #> [CmdletBinding()] param( [string]$csvFile="Users.csv", [string]$logFile="udm-dsa.log", [ValidateSet(",", ";", "`t")] [string]$delimiter = "`t", [switch]$Export ) #region: CMTraceLog Function formats logging in CMTrace style function CMTraceLog { Param ( [String]$Message, [String]$Component, [String]$ErrorMessage, [ValidateRange(1,3)] [Int]$Type, [Parameter(Mandatory=$true)] $LogFile ) $Time = Get-Date -Format "HH:mm:ss.ffffff" $Date = Get-Date -Format "MM-dd-yyyy" if ($ErrorMessage -ne "") {$Type = 3} if ($Component -eq $null) {$Component = " "} if ($Type -eq $null) {$Type = 1} $LogMessage = "<![LOG[$Message $ErrorMessage" + "]LOG]!><time=`"$Time`" date=`"$Date`" component=`"$Component`" context=`"`" type=`"$Type`" thread=`"`" file=`"`">" $LogMessage | Out-File -Append -Encoding UTF8 -FilePath $LogFile } #endregion #region: ADSI function Get-ADObjectDN function Get-ADObjectDN { param ( [Parameter(Mandatory=$true)] [String]$type, [String]$DNSDomainName="", [String]$CNObject ) if ("group","user","printqueue","computer" -NotContains $type) { Throw "$($type) is not valid! Please use 'group','user','printqueue','computer'" } $root = [ADSI]"LDAP://$DNSDomainName" $searcher = new-object System.DirectoryServices.DirectorySearcher($root) if ($type -eq "printqueue") { $searcher.filter = "(&(objectCategory=$type)(printerName=$CNObject))" } Elseif ($type -eq "computer") { $searcher.filter = "(&(objectCategory=$type)(sAMAccountName=$CNObject$))" } Else { $searcher.filter = "(&(objectCategory=$type)(sAMAccountName=$CNObject))" } $ADObject = $searcher.findall() if ($ADObject.count -gt 1) { $count = 0 foreach($objFound in $ADObject) { write-host $count ": " $objFound.path $count = $count + 1 } $selection = Read-Host "Please select item: " return $ADObject[$selection].path } else { return $ADObject[0].path } } #endregion #region: verify thata the logFile exists if(!(test-path "$pwd\log\$logFile")) { New-Item "$pwd\log\$logFile" -ItemType File } #endregion #region: Create hash CMTrace for splatting $hshCMTrace = @{ Message = "" Component = $(($MyInvocation.MyCommand.Name).TrimEnd('.ps1')) ErrorMessage = "" Type = 1 LogFile = "$pwd\log\$logFile" } #endregion #region: Reading CSV file. Stop if file isn't found Write-Verbose "Script started : $(Get-Date)`n" Write-Verbose "Reading CSV File $csvFile" if (test-path "$pwd\source\csv\$csvFile") { Write-Verbose "Importing csv file: $pwd\source\csv\$csvFile`n" $hshCMTrace.Message = "Importing csv file $csvFile" $hshCMTrace.Type = 1 CMTraceLog @hshCMTrace $csvUsers = Import-CSV "$pwd\source\csv\$csvFile" -delimiter $delimiter -Encoding UTF8 $LogDate = get-date -uformat "%d-%m-%Y" $exportFile = "$($LogDate)_$(($csvFile).TrimEnd('.csv'))_$(($MyInvocation.MyCommand.Name).TrimEnd('.ps1')).csv" $arrExportUsers = @() $usersCount = $(@($csvUsers).count) $indexUsers = 0 $indexMissing = 0 $indexFound = 0 } else { Write-Error -Message "File $csvFile not found...`n" -RecommendedAction "Please verify and try again...`n" $hshCMTrace.Message = "File $csvFile not found" $hshCMTrace.Type = 2 CMTraceLog @hshCMTrace exit } $hshCMTrace.Message = "Users count in $csvFile : $usersCount" $hshCMTrace.Type = 1 CMTraceLog @hshCMTrace #endregion #region: hashtable Progressbar $progFindUsers = @{ Activity = "Processing users in $($csvFile)" Status="Searching" CurrentOperation = "" PercentComplete = 0 } #endregion #region: hashtable UserAcccountControl #Have a look @site http://maxvit.net/userAccountControl $hshAccountControl =@{ 66048 = "NORMAL_ACCOUNT - ACCOUNT_ENABLED - DONT_EXPIRE_PASSWORD" 66080 = "NORMAL_ACCOUNT - ACCOUNT_ENABLED - DONT_EXPIRE_PASSWORD - PASSWD_NOTREQD" 512 = "NORMAL_ACCOUNT" 514 = "NORMAL_ACCOUNT - ACCOUNTDISABLE" 544 = "NORMAL_ACCOUNT - ACCOUNT_ENABLED - PASSWD_NOTREQD" 546 = "NORMAL_ACCOUNT - ACCOUNTDISABLE - PASSWD_NOTREQD" } #endregion Write-Verbose -Message "Script Started on $(get-date)" $ADDomainInfo = Get-ADDomain Write-Verbose -Message "Forest Distinguished Name: $($ADDomainInfo.DistinguishedName)" foreach ($user in $csvUsers) { $progFindUsers.Status = "Processing user $($user.SamAccountName)" $progFindUsers.PercentComplete = ($indexUsers/$usersCount) * 100 Write-Progress @progFindUsers #Get User DistinguishedName [ADSI]$ldapUser = Get-ADObjectDN -type "user" -DNSDomainName $ADDomainInfo.DistinguishedName -CNObject $user.SamAccountName if ($ldapUser -ne $null){ $value = $ldapUser.userAccountControl $ouIndex = $($ldapUser.DistinguishedName).IndexOf("OU=") $OU = ($ldapUser.DistinguishedName).Substring($ouIndex) $objUser = new-object psobject -Property @{ Name = $($ldapUser.Name) DistinguishedName =$($ldapUser.DistinguishedName) WhenCreated = $($ldapUser.WhenCreated) sAMAccountName =$($ldapUser.samACCountName) AccountControl = $hshAccountControl.Item($($value)) HomeDir =$($ldapUser.HomeDirectory) OU= $($OU) } $indexFound++ } else { $objUser = new-object psobject -Property @{ Name = $null DistinguishedName =$null WhenCreated = $null sAMAccountName =$($user.samACCountName) AccountControl = $null HomeDir =$null OU= $null } $indexMissing++ $hshCMTrace.Message = "Missing: $($user.SamAccountName)" $hshCMTrace.Type = 2 CMTraceLog @hshCMTrace } $arrExportUsers += $objUser $indexUsers++ } $hshCMTrace.Message = "Users found in $csvFile : $indexFound" $hshCMTrace.Type = 1 CMTraceLog @hshCMTrace $hshCMTrace.Message = "Users mssing in $csvFile : $indexMissing" $hshCMTrace.Type = 1 CMTraceLog @hshCMTrace if ($export) { "`r" Write-Verbose "Exporting results to $pwd\export\dsa\$exportFile" $arrExportUsers| select Name,SamAccountName,DistinguishedName,WhenCreated,AccountControl,HomeDir,OU | Export-CSV -NoTypeInformation "$pwd\export\dsa\$exportFile" -delimiter $delimiter -Encoding UTF8 } else { if (!($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent)) { $arrExportUsers | select Name,SamAccountName,DistinguishedName,WhenCreated,AccountControl,HomeDir,OU | Out-GridView -Title "Found Users $($csvFile) - $(Get-Date)" } } "`n" Write-Verbose "End script : $(Get-date)"
I’m all over the place! There’s a bit of everything!!! Trust me it works… Looking at it now, gotta ask myself, what was I thinking??? This was a script way back in the days when cmdlet performance was questionable… Rule of thumb if there’s a cmdlet use it! No need to reinvent the wheel…
So here’s a better (readable) version
$Prop = @('canonicalname','homedirectory','mail','homedrive','scriptpath','initials','profilepath','userprincipalname') import-csv -Path .\source\csv\moas-users.csv -Delimiter "`t" -Encoding UTF8 | ForEach-Object { get-aduser -Filter "SamAccountName -eq '$($_.SamAccountName)'" -properties $Prop | Select-Object Name,GivenName,SurName,Initials,mail,SamAccountName,Enabled,DistinguishedName,canonicalname, @{name='OU';expression={($_.DistinguishedName).SubString($_.DistinguishedName.indexof('OU='))}}, homedirectory,homedrive,scriptpath,profilepath,userprincipalname } | Out-GridView
This will get me the same information with waaay much lesser code… No need for complex verification,progress etc etc… Clean and simple… Processing each object as they go through the pipeline…
It is important to know beforehand what your objective is, is it a script or a tool? Don jones sums it up nicely stating:
“A scripts is something you usually make for yourself. Scripts are often quick and dirty, and although they might be long and complicated, they’re just a way for you to automate something that only you will ever do…”
Just because a script seems complicated doesn’t make it a tool, case in point. The former script is over engineered plain and simple. I got a bit carried away with all the possibilities in PowerShell.
If I had to start learning PowerShell today I’d advice myself to better understand and use the pipeline…
Hope it’s worth something to you!
Ttyl,
Urv