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