Welcome to ThePowerShellGuy.com Sign in | Join | Help

move-blog

I've moved the blog to my own domain. No further content will be updated here. The articles will remain for reference until I get all of them reposted to the new site.

 Here's the new link:  http://www.skyeagle.net/

 

Gaurhoth

 

Posted by Gaurhoth | (Comments Off)

Games 2008 Advanced Event 1 Solution

In next few posts, I'll be posting my solution for the Winter Scripting Games of 2008. Some I'll add comments. Some I won't :)

I found using Regex patterns far easier than the brute force method used by the Scripting Guys. See the code below:

 

$number = Read-Host "Enter Phone Number"

$telcomap = @("","","[abc]","[def]","[ghi]","[jkl]","[mno]","[prs]","[tuv]","[wxy]")

$number.GetEnumerator() | % -begin { [string]$s = "" } -process { [string]$s += [string]$telcomap[[int][string]$_] }

(gc "c:\scripts\wordlist.txt" | ? { $_ -match "^$($s)$" }) | Select-Object -First 1

Couple of things might need to be explained. First I used an array to hold the letter to number mappings. Then I generated a regex pattern based off of the number given. For example, with the number 7323464. The resulting regex pattern would be '[prs][def][abc][def][ghi][mno][ghi]'. Last line reads in the word list and compares each word with the regex pattern until a match is found (using the -match comparison operator).

 Gaurhoth

An example of how to use New-TaskPool

Those that have been reading my blog for awhile may remember the New-Taskpool script. Basically the script allows me to have multiple runspaces running simultaneously. If we give each runspace an object and a scriptblock... we can run through time consuming operations on several hundred objects a lot faster than doing them one at a time. This is especially helpful on WMI calls to remote machines where the majority of the time is waiting on a response. Just in case anyone has missed it earlier, I've attached the latest version of New-Taskpool to this post. In addition, you can visit the following search results to see the other articles I've written in the past on New-Taskpool.

Here’s a practical example of what I use New-TaskPool for. Generally I have between 300 and 600 computers alive on my LAN.  All of these computers are required by policy to have the latest version of Symantec AntiVirus installed and running. To verify this, I use the script found below.

Some things to note about the way New-TaskPool is used. You must pass it a ScriptBlock. You’ll see that I’ve assigned $code the contents of a scriptblock.

Keep in mind that you will not see ANY write-host, write-debug, or write-warning messages from the scriptblock as it’s being executed in a separate runspace and not visible to the current Host.  So any output you need, must be in the form of Write-Output. Errors HAVE to be handled (even if you just Trap { continue }) to avoid the runspace becoming stalled with ‘FAILED’ status. I could and probably should clean that up some in the New-Taskpool script – but that’s another blog entry for another day!

You’ll notice in the example, I’m using Get-NBTHosts. I blogged about that function a couple days ago. However, you can use anything that returns a collection of types shown in the switch { … } statement.

For example, any of the 3 would be valid:

$computers = [ADSI]"LDAP://CN=Computers,DC=testlab,DC=com"

$computers.psbase.get_children() | New-TaskPool -script $code -pool 10 | Format-Table Name,AVVersion,AVStatus,AVParent

############################

$computers = "TestMachine01","TestMachine02","TestMachine03"

$computers | New-TaskPool -scriptblock $code | Select-Object Name,AVVersion,AVStatus,AVParent

############################

## If you have PSCX installed:

Get-ADObject -class Computer | New-TaskPool -scriptblock $code | Select-Object Name,AVVersion,AVStatus,AVParent

   

Function Sweep-Sav {

    # Requires:

    #   new-taskpool v0.8 - Needs to be dot sourced as it's a function

    # Expects in Pipeline:

    #   DirectoryEntry (ADSI), SearchResult (directorysearcher), String with Computer Name or IP, Custom object with NAME property with Computer Name.

    # Example:

    #   Get-ADObject -class Computer | Is-Alive | New-TaskPool -script $code | Format-Table Name,AVVersion,AVStatus,AVParent

   

      # . .\New-TaskPool.ps1

     

    $code = {

        $input | % {

            $obj = $_

           

            # Accomodate Different Input object types.

            switch ($obj.psbase.gettype().name) {

                "DirectoryEntry"    { $cn = $obj.dnshostname[0] }

                "SearchResult"      { $cn = $obj.properties['dnshostname'][0] }

                "String"            { $cn = $obj.replace("IS~","").trim() }

                "PSCustomObject"    { $cn = $obj.Name }

            }

           

                  # Add 3 AV Related properties to the object

            $obj | add-member -name AVStatus -value $null -membertype NoteProperty -force

            $obj | add-member -name AVVersion -value $null -membertype NoteProperty -force

            $obj | add-Member -name AVParent -value $null -membertype NoteProperty -force

           

            # Return services that match "'Symantec AntiVirus' or 'Norton AntiVirus',

            $serv = get-wmiobject -co $cn -query "Select __SERVER,Name,Status,State from Win32_Service where Name='Symantec AntiVirus' or Name='Norton AntiVirus'" -ea silentlycontinue

           

                  ## $? = $true if last command completed successfully, $false if unsuccessful.

            if (!$?) {

                        ## WMI call failed to complete successfully.

                if ( $error[0].tostring() -match "require other privileges" ) {

                    $obj.AVStatus = "DENIEND"

                } elseif ( $error[0].tostring() -match "RPC server is unavailable" ) {

                    $obj.AVStatus = "OFFLINE" # or Firewalled

                }

            } elseif ((!$serv) -and $?) {

                        ## WMI completed successfully , but there was no matching service.

                $obj.AVStatus = "Not Installed"

            } elseif ($serv) {

                 $obj.AVStatus = $serv.state

               

                # Symantec stores the Version number in a registry key in

                # HKLM\Software\Intel\DLLUsage\VP6\ for rtvscan.exe

                $rk = [microsoft.win32.registrykey]::openremotebasekey([Microsoft.Win32.RegistryHive]::LocalMachine,$cn)

                $rr = $rk.opensubkey("SOFTWARE\INTEL\DLLUsage\VP6")

                if ($rr) {

                   

                    $obj.AVVersion = $rr.getvalue(($rr.getvaluenames() | select-String "rtvscan"))

                   

                    $rr = $rk.opensubkey("SOFTWARE\INTEL\LANDesk\VirusProtect6\CurrentVersion")

                    $obj.AVParent = $rr.getvalue("Parent")

                   

                } else {

               

                    # Try 64bit

                    $rr = $rk.opensubkey("SOFTWARE\Wow6432Node\INTEL\DLLUsage\VP6")

                    $obj.AVVersion = $rr.getvalue(($rr.getvaluenames() | select-String "rtvscan"))

                   

                    $rr = $rk.opensubkey("SOFTWARE\Wow6432Node\INTEL\LANDesk\VirusProtect6\CurrentVersion")

                    $obj.AVParent = $rr.getvalue("Parent")

                }

            }

 

            write-Output $obj

            

            trap {

                #I'm cheating and just ignoring any errors.

                continue

            }

        }

    }

 

    Get-NBTHosts 192.168.10.0/24 | New-TaskPool -scriptblock $code | Select-Object Name,AVVersion,AVStatus,AVParent

}

 

Comments Welcome as always:

 $emailaddress = [string]::join("",("gaurhoth",[char]64,"g","m","a","i","l",".","c","o","m"))
Posted by Gaurhoth | (Comments Off)
Attachment(s): New-TaskPool.txt

Get-NBTHosts

I've been a long time user of a very useful cmd line utility called NBTScan. NBTScan is similar to the Windows builtin cmd line utility NBTSTAT, but can operate on a range of IP addresses instead of a single IP. Using NBTscan, you can get a list of Windows machines that are configured to respond to NETBIOS and aren't behind a firewall. You can find some more information about NBTScan at http://www.unixwiz.net/tools/nbtscan.html, but that's an older version that doesn't support a couple of required parameters for this script. For the newest version that I've been able to find, look at http://inetcat.net/software/nbtscan.html.

Here's a script I use to wrap the output into Powershell friendly custom objects.

function Get-NBTHosts {

    param($iprange)

 

    if (!$iprange) {

      write-Host "You must specify an -iprange in CDIR notation.`r`n  Example: 192.168.1.0/24 "

      return

    }

 

   $iprange | % { nbtscan -t 500 -m 1 -s : $_  2>$null | % {

 

            $out = 1 |Select-Object IPAddress,Name,User,MACAddress

           

            $a = $_.split(":")

           

            $out.IPAddress = $a[0].trim()

            $out.Name = $a[1].replace("IS~","").trim()

            $out.User = $a[3].trim()

            $out.MACAddress = $a[4].trim()

           

            write-Output $out

        }

    }  

}

Example:

PS D:\ps> Get-NBTHosts 10.150.5.0/24

IPAddress                     Name                          User                          MACAddress
---------                     ----                          ----                          ----------
10.150.5.3                    HHHSSC65SP5                   <unknown>                     XX-XX-XX-XX-XX-XX
10.150.5.6                    HHHSQL                        <unknown>                     XX-XX-XX-XX-XX-XX
10.150.5.8                    HHHFDBSVR                     <unknown>                     XX-XX-XX-XX-XX-XX


PS D:\ps> 

You'll need nbtscan and cygwin1.dll somewhere in your path so that Get-NBTHosts can find it or modify the script to point directly to location of nbtscan.

Comments Welcome:

 $emailaddress = [string]::join("",("gaurhoth",[char]64,"g","m","a","i","l",".","c","o","m"))
Posted by Gaurhoth | (Comments Off)
Filed under: ,

New-HtmlHelp from VMWare modification

If you haven't already heard of New-HtmlHelp, you should visit http://blogs.vmware.com/vipowershell/2007/09/new-htmlhelp.html. One of their team members posted an nifty function to create a set of HTML pages from available Powershell Cmdlets documentation.

I took that function and modified a few lines to add an additional frame on the left side that lists each snapin represented by the list of commands. You can click 'All Cmdlets' to get the complete list or an individual Snap-In for a specific set of cmdlets.

Script is attached to this post.

UPDATE: By the way -- you need the doc-style.css in your current folder when you run the function. You can find it on the Vmware link above.

UPDATE: I was asked to provide an example of usage:

PS D:\ps\functions> New-HtmlHelp (get-command)

Updated to make the modifications optional (use the -groupsnapins switch to get the new behaviour). [September 23, 2007 - 5:06pm EDT]

new-htmlhelp

Comments welcome:

 $emailaddress = [string]::join("",("gaurhoth",[char]64,"g","m","a","i","l",".","c","o","m"))

Using Powershell to Mail-Enable an AD User without CDOEXM

Over on microsoft.public.windows.powershell, it’s been asked how to mail-enable an AD user in an Exchange 2003 Environment. The MS supported way involves CDOEXM (part of Exchange System Manager) which seems to be rather difficult to use from powershell.

In reality, you can usually get away with using [ADSI] to modify several key attributes on the AD user and let RUS initailize the remaining attributes to create the mail-enabled user.

Here’s the example I posted on Usenet:

$user = [ADSI]"LDAP://CN=Powershell Test,OU=Standard Users,OU=Site1,DC=testlab,DC=com"

$user.mailNickname = "ptest"

$user.msExchHomeServerName = "/o=testlab/ou=First Administrative Group/cn=Configuration/cn=Servers/cn=SITE1-03EX1"

$user.setinfo()

#wait for RUS - This can be seconds or hours depending on your Exchange configuration.

# To see the changes, reassign the modified AD object to $user

$user = [ADSI]"LDAP://CN=Powershell Test,OU=Standard Users,OU=Site1,DC=testlab,DC=com"

$user | fl *

 

The minimum attributes needed for RUS are mailNickname (Exchange Alias) and msExchHomeServerName.  You can see the format that msExchHomeServerName expects in the example and this will obviously be different for every exchange server. If you don’t fill in any additional attributes, the mailbox will be created in the default mail store on the server specified. If you need to point to a different mail store, you can use the homeMDB attribute.  Again, the easiest way to see the expected format is to grab an already mail-enabled user and take a look at the attributes:

 

PS C:\> $user = [ADSI]"LDAP://CN=Powershell Test,OU=Standard Users,OU=Site1,DC=testlab,DC=com"                          
PS C:\> $user | format-list cn,mailNickname,msExchHomeServerName,homeMDB,homeMTA,proxyAddresses                         
                                                                                                                        
                                                                                                                        
cn                   : {Powershell Test}                                                                                
mailNickname         : {ptest}                                                                                          
msExchHomeServerName : {/o=testlab/ou=First Administrative Group/cn=Configuration/cn=Servers/cn=SITE1-03EX1}            
homeMDB              : {CN=Mailbox Store (SITE1-03EX1),CN=First Storage Group,CN=InformationStore,CN=SITE1-03EX1,CN=Ser 
                       vers,CN=First Administrative Group,CN=Administrative Groups,CN=testlab,CN=Microsoft Exchange,CN= 
                       Services,CN=Configuration,DC=testlab,DC=com}                                                     
homeMTA              : {CN=Microsoft MTA,CN=SITE1-03EX1,CN=Servers,CN=First Administrative Group,CN=Administrative Grou 
                       ps,CN=testlab,CN=Microsoft Exchange,CN=Services,CN=Configuration,DC=testlab,DC=com}              
proxyAddresses       : {SMTP:ptest@testlab.com, X400:c=US;a= ;p=testlab;o=Exchange;s=Test;g=Powershell;}                
                                                                                                                        
                                                                                                                        
                                                                                                                        

 

After you assign the necessary attributes and issue the .setInfo() method, you just have to wait for RUS to do it’s magic. This can take seconds or much longer. It just depends on how the Exchange environment is configured.

 

As with anything that directly modifies AD attributes… TEST, TEST and TEST some more in a LAB. Never start in a production environment.

 

gaurhoth

 

* Edited to reflect that this article is about Exchange 2003.

New-TaskPool v0.8 Bug Fix

It’s been a very long time since my last post. Excuses include an insane job situation involving a recent manager's departure as well as my recent purchase of a new home that has occupied most every free minute.

So with that out of the way, here’s an update to my New-TaskPool script that fixes a bug where you could get an error stating, “Pipeline not executed because a pipeline is already executing.”

Nothing else has changed.

Posted by Gaurhoth | (Comments Off)
Attachment(s): New-TaskPool.txt

New-TaskPool.ps1 - Threading the Powershell way Pt 2

I’ve made a few modifications to New-TaskPool. My original script couldn’t easily be loaded into your profile. You had to specify the script file ( .\New-TaskPool.ps1 ). This quickly became a problem as I would invariably be in the wrong folder and typing out the full path tested my patience. With that in mind, I made modifications that allowed me to encapsulate New-TaskPool into a function that could be loaded from my profile. Overall the usage is very similar. You'll find the full script attached to this post.

You may also notice that I’ve started using a collection of a custom object instead of 5 or 6 separate collections for different variables. So:

$script:rscfg = @(); (0..($pools-1)) | % { $rscfg += 0 }

$script:rs = @(); (0..($pools-1)) | % { $rs += 0 }

$script:pipe = @(); (0..($pools-1)) | % { $pipe += 0 }

$script:pipetimer = @(); (0..($pools-1)) | % { $pipetimer += 0 }

$script:cache = @(); (0..($pools-1)) | % { $cache += 0 }

Has become:

$pipemgr = @(); (0..($pools-1)) | % { $pipemgr += 0 | select-object rscfg,rs,pipe,pipetimer,input,error,output }

In addition, I’ve increased performance overall. Instead of recreating the runspace and pipeline for each job, I’m reusing the runsapce and just creating a new pipeline. In one of my speed comparisons, I had 300 items and a “dummy” script block that did nothing but write-output the input. For those 300 items under my original New-TaskPool, it took 31 seconds. With the modifications show in this article, it’s down to 1.5 seconds. Whoo! This is still slightly experimental, so I would like to hear if anyone has a problem with the way this is working now.

Since New-TaskPool is now a function, you can either load it via your profile or just dot-source it in your console when needed. Here’s an example of it’s usage:

215# # Example scriptblock
216# $block = {
>>     $input | % {
>>         $fixed = gwmi -co $_ -class win32_quickfixengineering -ea silentlycontinue | ? { $_.HotFixID -eq "KB931836" }

>>         write-Output $fixed
>>     }
>> }
>>
217# # Dot-Source function into your current session:
218# . .\New-TaskPool.ps1
219#
219# "testmachine","testmachine","testmachine","testmachine" | new-taskpool -scriptblock $block


Description         : Update for Windows Server 2003 (KB931836)
FixComments         : Update
HotFixID            : KB931836
Install Date        :
InstalledBy         : SYSTEM
InstalledOn         : 2/14/2007
Name                :
ServicePackInEffect : SP3
Status              :

Description         : Update for Windows Server 2003 (KB931836)
FixComments         : Update
HotFixID            : KB931836
Install Date        :
InstalledBy         : SYSTEM
InstalledOn         : 2/14/2007
Name                :
ServicePackInEffect : SP3
Status              :

Description         : Update for Windows Server 2003 (KB931836)
FixComments         : Update
HotFixID            : KB931836
Install Date        :
InstalledBy         : SYSTEM
InstalledOn         : 2/14/2007
Name                :
ServicePackInEffect : SP3
Status              :

Description         : Update for Windows Server 2003 (KB931836)
FixComments         : Update
HotFixID            : KB931836
Install Date        :
InstalledBy         : SYSTEM
InstalledOn         : 2/14/2007
Name                :
ServicePackInEffect : SP3
Status              :

Tomorrow, I will be posting an example scriptblock that I use on a daily basis to scan the network for AntiVirus compliance.

Gaurhoth

Posted by Gaurhoth | (Comments Off)
Attachment(s): New-TaskPool.txt

Finding a Range of IP addresses

A recent question in Microsoft.public.windows.powershell asked for ideas on working with a range of IP addresses expressed in CIDR notation (192.168.1.0/24). The intent is to get a list of IP addresses inside the range. I posted a small sample in the group, but here is one that’s cleaned up a little bit and will accept pipeline input. You can see the original usenet article here.

Get-IPRange supports input in the standard CIDR notation which looks like 192.168.1.0/24 or 10.132.0.0/16, etc. It also supports 3 switches that allow you to control how the results are returned.

                -AsString – Returns string representation of the IP in Dot-Decimal Format (i.e. “192.168.1.1”)

                -AsDecimal – Returns decimal representation of the  IP (I.e. 3232235777)

                -AsIPAddress – Returns a [System.Net.IPAddress] object representing the IP address. This is the default if no other switches are specified.

You can also pass the IP Range via the –IPAddress parameter or via the Pipeline. Examples of each:

Get-IPRange –IPAddress “192.168.1.1/24

 

192.168.1.0/24”,”192.168.10.0/22” | Get-IPRange

Here is the function. Feel free to let me know of any bugs, but be gentle as I know I'm not validating a lot of the input :)

Function Get-IPRange {

    param([string]$ipaddress="",[switch]$AsString,[switch]$AsDecimal,[switch]$AsIPAddress)

   

    begin {

        # support functions

        function ToDecimal {

            # Convert a string IP ("192.168.1.1") to it's Decimal Equivalent

            # Convert a Binary IP ("11011011111101011101110001111111") to it's Decimal Equivalent

            param([string]$paddress)

           

            if ($paddress.length -gt 15) {

                # Possibly Binary as it's too long for Dot-Decimal

                return [system.Convert]::ToInt64($paddress.replace(".",""),2)

            } else {

                # Possibly Dot-Decimal.

                # Converting an IP address to decimal involves shifting bits

                # for each octect. Powershell doesn't have any bit shifting operators

                # so we'll use some math (YUCK!)

                [byte[]]$b = ([system.Net.IPAddress]::Parse($paddress)).GetAddressBytes()

                $longip = 0

                for ($i = 0; $i -lt 4; $i++) {

                    $num = $b[$i]

                    $longip += (($num % 256) * ([math]::pow(256,3-$i)))

                }

                return [int64]$longip

            }

        }

       

        function ToIP {

            param ($paddress)

           

            if ($paddress.Length -gt 15) {

                # Possibly Binary as it's too long for Decimal

                # If it's not a string, it'll also fail this test

                 return [system.Net.IPAddress]::Parse( (ToDecimal $paddress) )

            } else {

                # Most likely Decimal which the Parse method understands on its own.

                return [system.Net.IPAddress]::Parse($paddress)

            }

        }

       

        function ToBinary {

            param($paddress)

           

            if ($paddress.GetType().Name -eq "string") {

                #Dot-Decimal

                return ([system.Convert]::ToString((ToDecimal $paddress),2)).padleft(32,[char]"0")

            } else {

                #Decimal

                return ([system.Convert]::ToString($paddress,2)).padleft(32,[char]"0")

            }

        }

 

        function SplitAddress {

            param([string]$paddress)

            $address = ($paddress.split("/")[0])

            $cidr = ([int]($paddress.split("/")[1]))

            if (($cidr -lt 0) -or ($cidr -gt 32)) {

                #invalid CIDR.

                throw "$_ is not a valid CIDR notation for a range of IP addresses."

            } else {

                write-Output $address

                write-Output $cidr

            }

        }

       

    }

   

    process {

       

        ################################################

        # Support both pipeline and argument input.

        if ($_) {

            # Pipeline Input

            $address,$cidr = SplitAddress $_

        } else {

            # Argument Input

            if (!$ipaddress) { Throw "You must specify an IP Range in CIDR notation (I.e. `"192.168.1.0/24`")" }

            $address,$cidr = SplitAddress $ipaddress

        }

 

        $binaddress = ToBinary ( ToDecimal $address )

        $binmask = ("1" * $cidr).padright(32,[char]"0")

   

        $binnetwork = ""

        $binbroadcast = ""

        for ($i = 0; $i -lt 32; $i++) {

            # faking a bitwise comparison since powershell's -BAND only handles Int32.

            # Determine the Network Address (first in range) by doing a bitwise AND

            # between the address and mask specified.

            $binnetwork += [string]( $binmask.Substring($i,1) -band $binaddress.substring($i,1) )

            # Determine the Broadcast Address by flipping only the HOST bits to 1

            if ($i -lt $cidr) {

                $binbroadcast += [string]( $binaddress.Substring($i,1) )

            } else {

                $binbroadcast += [string]"1"

            }

        }

       

        # Convert the binary results back to Decimal

        $longnetwork =  ToDecimal $binnetwork

        $longbroadcast =  ToDecimal $binbroadcast

       

        #Pipe each IP object up the pipeline:

        # I'm skipping the network address and the Broadcast address

        for ($i = $longnetwork+1; $i -lt $longbroadcast; $i++) {

            if ($AsString) {

                write-Output (ToIP $i).IPAddressToString

            } elseif ($AsDecimal) {

                write-Output $i

            } else {

                #AsIPAddress

                write-Output (ToIP $i)

            }

        }

    }

}

 

Posted by Gaurhoth | (Comments Off)
Filed under: , ,

Comparing Win32_PingStatus vs [System.Net.NetworkInformation.Ping]

Back again with another comparison between .NET and WMI performance. While WMI can be very useful and often the only way to accomplish some remote oriented tasks, you will rarely hear anyone that has worked with WMI classes for long brag about its performance. Whenever possible I try to discover alternatives to WMI and I’ve found what I think is another candidate.

The most popular method of pinging in powershell so far seems to be the Win32_PingStatus WMI class since it’s easily available in powershell and doesn’t involve the use of an external command (ping.exe). For the test, I’m just going to create an array of 50 items all containing the same computername (which for this test is pointing to the local machine). Ping responses should be as nearly instantaneously as possible. I’ll grant there are a lot of factors that could affect such a simple test, but the results are promising enough.

The Array:

$g = @("testmachine")*50

So we now have an array of 50 identical computer names. Let’s pipe it through the most common ping function that I’ve seen which I found at http://blog.sapien.com/current/2006/12/8/more-pinging.html.

Function Ping-Name { 

    PROCESS {

        $wmi = get-wmiobject -query "SELECT * FROM Win32_PingStatus WHERE Address = '$_'"

        if ($wmi.StatusCode -eq 0) { $_ }

    }

}

And now for a trip through Measure-Command:

23# measure-command { $g | Ping-Name }


Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 542
Ticks             : 5427557
TotalDays         : 6.28189467592593E-06
TotalHours        : 0.000150765472222222
TotalMinutes      : 0.00904592833333333
TotalSeconds      : 0.5427557
TotalMilliseconds : 542.7557

Not bad. That didn’t take very long. Now let’s look at a ping function that uses the [System.Net.NetworkInformation.Ping] .NET class.

Function Ping-Net {

    Begin { $ping = new-object System.Net.NetworkInformation.Ping }

    Process {

        if ($ping.Send($_)) {

            $_

        }

    }

}

And again, let’s test the performance using Measure-Command:

41# measure-command { $g | Ping-Net }


Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 38
Ticks             : 385107
TotalDays         : 4.45725694444444E-07
TotalHours        : 1.06974166666667E-05
TotalMinutes      : 0.000641845
TotalSeconds      : 0.0385107
TotalMilliseconds : 38.5107

Even faster. I don’t know about you, but I like faster even if it is barely noticeable J

 

Posted by Gaurhoth | (Comments Off)
Filed under: , ,

New-TaskPool.ps1 - Threading the Powershell way

For a while now I've been trying to perfect a script that will let me simulate having multiple threads in powershell. Here's what I've come up with. The entire script is attached to the post. I'll only be discussing a few bits and pieces today. Feel free to leave any questions in the comments section.

The general idea of the script is that you give it a scriptblock as an argument and then pass items to it via the pipeline. It’ll take each input and pass it into an available “runspace pool” if there is one. If not, it waits for an available pool to open up. As each input is processed, the script writes the output back up the pipeline. An example might make things a bit clearer.

Let’s say you want to scan a list of computer names to see if they have a particular hotfix. You can do this remotely using get-wmiobject and the win32_quickfixengeneering class. Something like:

gwmi -co "testmachine" -class win32_quickfixengineering -ea silentlycontinue | ? { $_.HotFixID -eq "KB931836" }

Let’s see how long this takes to run on just 4 computers (yes, I used the same computer over and over… this is just an example):

14# measure-command { "testmachine","testmachine","testmachine","testmachine" | % { gwmi -co $_ -class win32_quickfixeng
ineering -ea silentlycontinue | ? { $_.HotFixID -eq "KB931836" } } }


Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 43
Milliseconds      : 662
Ticks             : 436625205
TotalDays         : 0.000505353246527778
TotalHours        : 0.0121284779166667
TotalMinutes      : 0.727708675
TotalSeconds      : 43.6625205
TotalMilliseconds : 43662.5205

The times are long because I’m scanning the computer across a WAN connection, but considering I work with 7 physical sites…this is a good test.

Now let’s see the same thing but fed to New-TaskPool as a scriptblock:

24# $block = {
>>     $input | % {
>>        $fixed = gwmi -co $_ -class win32_quickfixengineering -ea silentlycontinue | ? { $_.HotFixID -eq "KB931836" }

>>        write-Output $fixed
>>     }
>> }
>>
25# "testmachine","testmachine","testmachine","testmachine" | .\New-TaskPool.ps1 -scriptblock $block


Description         : Update for Windows Server 2003 (KB931836)
FixComments         : Update
HotFixID            : KB931836
Install Date        :
InstalledBy         : SYSTEM
InstalledOn         : 2/14/2007
Name                :
ServicePackInEffect : SP3
Status              :

Description         : Update for Windows Server 2003 (KB931836)
FixComments         : Update
HotFixID            : KB931836
Install Date        :
InstalledBy         : SYSTEM
InstalledOn         : 2/14/2007
Name                :
ServicePackInEffect : SP3
Status              :

Description         : Update for Windows Server 2003 (KB931836)
FixComments         : Update
HotFixID            : KB931836
Install Date        :
InstalledBy         : SYSTEM
InstalledOn         : 2/14/2007
Name                :
ServicePackInEffect : SP3
Status              :

Description         : Update for Windows Server 2003 (KB931836)
FixComments         : Update
HotFixID            : KB931836
Install Date        :
InstalledBy         : SYSTEM
InstalledOn         : 2/14/2007
Name                :
ServicePackInEffect : SP3
Status              :

And does it make any speed difference?

21# measure-command { "testmachine","testmachine","testmachine","testmachine" | .\new-taskpool.ps1 -scriptblock { $input
 | % {gwmi -co $_ -class win32_quickfixengineering -ea silentlycontinue | ? { $_.HotFixID -eq "KB931836" } } } }


Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 10
Milliseconds      : 410
Ticks             : 104106992
TotalDays         : 0.000120494203703704
TotalHours        : 0.00289186088888889
TotalMinutes      : 0.173511653333333
TotalSeconds      : 10.4106992
TotalMilliseconds : 10410.6992

4 times faster (which makes sense because all 4 gwmi queries where happening simultaneously). Obviously, this scriptblock is insanely simple so you’ll see the most value of this with scriptblocks that take a little time to complete.

Before I leave, one thing to point out is that your scriptblock needs to be expecting pipeline input, so you have to use something like:

$input | % {

    ## Do whatever you want here and finally

    ## output results.

    Write-Output $_

}

Obviously, this is just echoing the input back into the output.

I’ll stop here for now, but will continue with some additional entries later that discuss the other arguments (such as –failblock) and give a couple real-world scriptblock examples that I use.

gaurhoth

Posted by Gaurhoth | (Comments Off)
Filed under: ,

Attachment(s): new-taskpool.txt

"Gaurhoth's Powershell Thoughts" | move-blog

I wanted to start with a thanks to /\/\o\/\/ for letting me invade his site and set up my own blog here. I've posted the occassional blog entry at http://gaurhothw.spaces.live.com/ but wasn't happy with the interface for a number of uninteresting reasons. I'll be moving a couple of the blog entries (the ones that I like) from the old blog over here as time allows.

Anyway, feel free to come on in and put your feet up. This blog will be almost totally centered around Powershell with the occassional non-powershell rant thrown in for variety.

You can also find me hanging around in #Powershell on Freenode.

gaurhoth

Posted by Gaurhoth | (Comments Off)

Querying date based Active Directory fields

Another “from usenet to blog” entry.  Someone wanted to a list of all users in their Active Directory created after a specific day. Like most AD related tasks, this is fairly easy in Powershell, but you do have to be aware of one tricky piece. LDAP queries require a specially formatted string to represent date/time. For this, we’ll query the ‘whenCreated’ field of the AD. Here’s an example that returns all users from AD that were created in the last 15 days:

$past = [datetime]::UtcNow.adddays(-15)

$ldappast = "{0:0000}{1:00}{2:00}000000.0Z" -f $past.year,$past.month,$past.day

$s = new-object directoryservices.directorysearcher([ADSI]'')

$s.filter = "(&(objectcategory=person)(objectclass=user)(whenCreated>=$ldappast))"

$s.findall()

$ldappast holds the date in the specific format needed when comparing date/times in LDAP: YYYYMMDDHHMMSS.TZ. An example of March 12, 2007 00:00 represented in this format would be: 20070312000000.0Z. By the way, “0Z” indicates UTC.

 

gaurhoth

Listing Remote Services without using WMI

WMI seems to get recommended for most "remote" queries in powershell. This is due in large part because the get-wmiobject cmdlet includes built in support for querying remote machines. However, I've found that WMI can be slow and whenever I find a dotnet alternative, I like to compare the speed of each method.

One of my scripts remotely queries the list of services running looking for status of a particular antivirus services. For the longest time, I relied on WMI and the -filter parameter. Recently I saw information on the [serviceprocess.servicecontroller] class and decided to give it ago. Suffice to say the performance difference is huge! Here's both versions along with results of measure-command:

WMI:

$cn = "testmachine"

$serv = gwmi -co $cn -class win32_service

Now let's wrap it with measure-command and see how long it's taking (on my system):

84# measure-Command {
>>     $cn = "testmachine"
>>     $serv = gwmi -co $cn -class win32_service
>> }
>>


Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 375
Ticks             : 3755809
TotalDays         : 4.34700115740741E-06
TotalHours        : 0.000104328027777778
TotalMinutes      : 0.00625968166666667
TotalSeconds      : 0.3755809
TotalMilliseconds : 375.5809

NET:

$cn = "testmachine"

[system.reflection.assembly]::LoadWithPartialName("system.serviceprocess") | out-null

$serv = [serviceprocess.servicecontroller]::GetServices($cn)

And with measure-command:

92# measure-Command {
>>     $cn = "testmachine"
>>     [system.reflection.assembly]::LoadWithPartialName("system.serviceprocess") | out-null
>>     $serv = [serviceprocess.servicecontroller]::GetServices($cn)
>> }
>>


Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 25
Ticks             : 259210
TotalDays         : 3.00011574074074E-07
TotalHours        : 7.20027777777778E-06
TotalMinutes      : 0.000432016666666667
TotalSeconds      : 0.025921
TotalMilliseconds : 25.921

As you can see the speed is quite measurable. I ran this several times on my system and the results were consistent.

If you look at the output from each command, you'll notice that they each return a different set of properties, so you can't always find a direct drop in replacement for WMI, but in my case, the net classes had the properties I was interested in.

gaurhoth

Posted by Gaurhoth | (Comments Off)
Filed under: , , ,

Active Directory - Paged Queries

One question that has come up a couple of times in the newsgroup 'microsoft.public.windows.powershell' is how to get more than 1000 records from an Active Directory query, particularly when using get-adobject (from the Powershell Community Extensions). Unfortunately, for the moment there is no way to retrieve more records using get-adobject. I'm sure the PSCX team is working to add that for a future release, but in the mean time, here's how you can accomplish the same results using the [directoryservices.directorysearcher] class and the .PageSize property.

$de = [ADSI]''

$ads = new-object directoryservices.directorysearcher

$ads.searchroot = $de

$ads.filter = "(&(objectcategory=person)(objectclass=user))"

$ads.pagesize = 100

$g = $ads.findall()

$g.count

The magic is in the $ads.pagesize = 100 line which tells the query to return up to 100 "pages" of results (with default being 1000 records per page unless you modify a setting on the Domain Controller itself).

The one difference between using directoryservices.directorysearcher and get-adobject is that directorysearcher actually returns a SearchResultCollection. If you want the actual directoryentry (which is what get-adobject returns), you can modify $g = $ads.findall() to:

$g = $ads.findall() | % { $_.GetDirectoryEntry() }

This will fill $g with DirectoryEntry objects instead of just the SearchResult objects.

gaurhoth