Posts RSS Comments RSS 253 Posts and 407 Comments till now

blog: Sick of WMI timeouts stopping your script? Try Test-Host!

I manage a large number of servers and this management involves a ton of WMI queries. A long time back I got sick of waiting and waiting for my WMI queries so I developed a little script called Test-Port that would do a WMI ping or would test a specific port (for firewalls) and pass the object on if connectivity passed.

You can see more details about that script here: Test-Port (kinda like portqry without verbose output)

With v2 just on the horizon I wanted to see what I could improve using Powershell v2 and decided to re-write the code (found below.)

Some of the new features are:
- Rename to Test-Host
- Built in Help and examples (biggy)
- Parameter Binding
- Added more logging with -verbose switch

This can be very useful if you ever have the need to do a task on a number of Computers (specifically WMI.) For example, If I needed to get all the machines running x64 in my domain I could something like this:

  1. $computers = Get-QADComputer -search $OU | %{$_.dnshostname} | Test-Host -tcp 135
  2. $query = "SELECT * FROM win32_processor WHERE addresswidth='64'"
  3. Get-WMIObject -query $query -comp $computers -asJob

Here is the code for Test-Host
Can download here: Test-Host.ps1 (from Poshcode.org)
  1. function Test-Host
  2. {
  3.  
  4. [CmdletBinding()]
  5.  
  6. Param(
  7. [Parameter(ValueFromPipeline=$true,Mandatory=$True)]
  8. [string]$Server,
  9. [Parameter()]
  10. [int]$TCPPort,
  11. [Parameter()]
  12. [int]$timeout=1000,
  13. [Parameter()]
  14. [string]$property
  15. )
  16. Begin
  17. {
  18. function TestPort {
  19. Param($srv,$tport,$tmOut)
  20. Write-Verbose " [TestPort] :: Start"
  21. Write-Verbose " [TestPort] :: Setting Error state = 0"
  22. $Error\3ActionPreference = "SilentlyContinue"
  23.  
  24. Write-Verbose " [TestPort] :: Creating [system.Net.Sockets.TcpClient] instance"
  25. $tcpclient = New-Object system.Net.Sockets.TcpClient
  26.  
  27. Write-Verbose " [TestPort] :: Calling BeginConnect($srv,$tport,$null,$null)"
  28. $iar = $tcpclient.BeginConnect($srv,$tport,$null,$null)
  29.  
  30. Write-Verbose " [TestPort] :: Waiting for timeout [$timeout]"
  31. $wait = $iar.AsyncWaitHandle.WaitOne($tmOut,$false)
  32. # Traps
  33. trap
  34. {
  35. Write-Verbose " [TestPort] :: General Exception"
  36. Write-Verbose " [TestPort] :: End"
  37. return $false
  38. }
  39. trap [System.Net.Sockets.SocketException]
  40. {
  41. Write-Verbose " [TestPort] :: Exception: $($_.exception.message)"
  42. Write-Verbose " [TestPort] :: End"
  43. return $false
  44. }
  45. if(!$wait)
  46. {
  47. $tcpclient.Close()
  48. Write-Verbose " [TestPort] :: Connection Timeout"
  49. Write-Verbose " [TestPort] :: End"
  50. return $false
  51. }
  52. else
  53. {
  54. Write-Verbose " [TestPort] :: Closing TCP Sockett"
  55. $tcpclient.EndConnect($iar) | out-Null
  56. $tcpclient.Close()
  57. }
  58. if($?){Write-Verbose " [TestPort] :: End";return $true}
  59. }
  60. function PingServer {
  61. Param($MyHost)
  62. Write-Verbose " [PingServer] :: Pinging $MyHost"
  63. $pingresult = Get-WmiObject win32_pingstatus -f "address='$MyHost'"
  64. Write-Verbose " [PingServer] :: Ping returned $($pingresult.statuscode)"
  65. if($pingresult.statuscode -eq 0) {$true} else {$false}
  66. }
  67. }
  68. Process
  69. {
  70. Write-Verbose ""
  71. Write-Verbose " Server : $Server"
  72. if($TCPPort)
  73. {
  74. Write-Verbose " Timeout : $timeout"
  75. Write-Verbose " Port : $TCPPort"
  76. if($property)
  77. {
  78. Write-Verbose " Property : $Property"
  79. if(TestPort $Server.$property -tport $TCPPort -tmOut $timeout){$Server}
  80. }
  81. else
  82. {
  83. if(TestPort $Server -tport $TCPPort -tmOut $timeout){$Server}
  84. }
  85. }
  86. else
  87. {
  88. if($property)
  89. {
  90. Write-Verbose " Property : $Property"
  91. if(PingServer $Server.$property){$Server}
  92. }
  93. else
  94. {
  95. Write-Verbose " Simple Ping"
  96. if(PingServer $Server){$Server}
  97. }
  98. }
  99. Write-Verbose ""
  100. }
  101. }

Testing AD LDS (ADAM) replication with Powershell

Earlier this month I had a discussion with Laura (of AD Cookbook fame) regarding ADLDS and how to test convergence. After a few minutes I remembered I had a AD convergence script I wrote a while back found HERE. With a little tweaking (specifically discoverability) we converted it to test ADLDS as well. Below you will fine the result.

Parameters
- Server: The ADLDS/ADAM server that hosts the application partition you want to test
- DN: The distinguished name of the application partition you want to test (will try to discover)
- Port: Port ADLDS/ADAM list on (Default 389)
- Table [switch]: A switch that outputs an object with the results.

Note: Please feel free to provide any feedback you have regarding this. I do not use ADLDS or ADAM so other than my test environment I really cannot play with this.

The Code
Test-LDSReplication.ps1


 

Param($Server = $Env:ComputerName,
      $DN,
      $Port = "389",
      [switch]$table
      )

function Ping-Server {
   Param([string]$srv)
   $pingresult = Get-WmiObject win32_pingstatus -f "address=’$srv’ and Timeout=1000"
   if($pingresult.statuscode -eq 0) {$true} else {$false}
}

$DirectoryServer = "{0}:{1}" -f $Server,$Port

$Context = new-object System.DirectoryServices.ActiveDirectory.DirectoryContext("DirectoryServer",$DirectoryServer)
$ADAM = [System.DirectoryServices.ActiveDirectory.AdamInstance]::GetAdamInstance($context)

if(!$DN)
{
    $AppPartition = $ADAM.ConfigurationSet | %{$_.ApplicationPartitions} | Select-Object -first 1
    $DN = $AppPartition.Name
    $dclist = $AppPartition.DirectoryServers | ?{$_.HostName -notmatch $Server}
}
else
{
    $dclist = $ADAM.ConfigurationSet.AdamInstances | ?{($_.Partitions -contains $DN) -and ($_.HostName -notmatch $Server)}
}

if($table)
{
    $DCTable = @()
    $myobj = "" | select Name,Time
    $myobj.Name = ("$Server [SOURCE]").ToUpper()
    $myobj.Time = 0.00
    $DCTable += $myobj
}

$timestamp = [datetime]::Now.ToFileTime().ToString()
Write-Host "`n  Modifying wwwHomePage Attribute on Object [$DN] on [$DirectoryServer] with value [$timestamp]"
$object = ([ADSI]"LDAP://$DirectoryServer/$DN")
$object.wWWHomePage = $timeStamp
$object.SetInfo()
$objectDN = $object.distinguishedname
Write-Host "  Object [$objectdn] Modified! `n"

$start = Get-Date

$i = 0

Write-Host "  Found [$($dclist.count)] LDS replicas"
$cont = $true

While($cont)
{
    $i++
    $oldpos = $host.UI.RawUI.CursorPosition
    Write-Host "  =========== Check $i ===========" -fore white
    start-Sleep 1
    $replicated = $true
    foreach($dc in $dclist)
    {
        if($server -match $dc.HostName){continue}
        if(ping-server $dc.HostName)
        {
            $DCServer = "{0}:{1}" -f $dc.HostName,$dc.LdapPort
            $object = [ADSI]"LDAP://$DCServer/$dn"
            if($object.wwwHomePage -eq $timeStamp)
            {
                Write-Host "  - $DCServer Has Object Description [$dn]" (" "*5) -fore Green
                if($table -and !($dctable | ?{$_.Name -match $dc.HostName}))
                {
                    $myobj = "" | Select-Object Name,Time
                    $myobj.Name = $dc.HostName.ToUpper()
                    $myobj.Time = ("{0:n2}" -f ((Get-Date)-$start).TotalSeconds)
                    $dctable += $myobj
                }
            }
            else{Write-Host "  ! $($dc.HostName.ToUpper()) Missing Object [$dn]" -fore Red;$replicated  = $false}
        }
        else
        {
            Write-Host "  ! $($dc.HostName.ToUpper()) Failed PING" -fore Red
            if($table -and !($dctable | ?{$_.Name -match $dc}))
            {
                $myobj = "" | Select-Object Name,Time
                $myobj.Name = $dc.HostName.ToUpper()
                $myobj.Time = "N/A"
                $dctable += $myobj
            }
        }
    }
    if($replicated){$cont = $false}else{$host.UI.RawUI.CursorPosition = $oldpos}
}

$end = Get-Date
$duration = "{0:n2}" -f ($end.Subtract($start).TotalSeconds)
Write-Host "`n    Took $duration Seconds `n" -fore Yellow

if($table){$dctable | Sort-Object Time}

Test-CitrixHotfix

I often find the need to compare the state of my PS servers. This little script allows me to find all the Citrix hotfixes for a group of server(s), or list of server(s) that have a specific hotfix.

Parameters:
$File: The name of the file which contains a list of servers
$Filter: Name or Regex of the Hotfix(es) to return
$Server: Name of the server to check

What it returns (a custom object):
ServerName: Name of the Server with the Hotfix(es)
Hotfixes: List of hotfix names [String[]]
RawObjects: Array of MFCOM MetaFrameHotfix objects

Examples
To List all the Hotfixes on a list of servers
.\Test-CitrixHotfix.ps1 ServerList.txt

To List a Specific hotfix on a list of servers
.\Test-CitrixHotfix.ps1 ServerList.txt -filter PSE450W2K3021

To List all the hotfixes on a specific Server
.\Test-CitrixHotfix.ps1 -s Server1

To List a specific hotfix for a specific server
.\Test-CitrixHotfix.ps1 -s Server1 -filter PSE450W2K3021

The Code:

Param($file,$filter=".*",$server)

Function Ping-Server {
   Param([string]$server)
   $pingresult = Get-WmiObject win32_pingstatus -f "address=’$Server’"
   if($pingresult.statuscode -eq 0) {$true} else {$false}
}
Function Check-CitrixHotfix{
    Param([string]$server)
    $mf = New-Object -ComObject MetaframeCOM.MetaframeFarm
    $mf.Initialize(1)
    $srv = $mf.GetServer2(6,$server)
    $list = $srv.winServerObject2.hotfixes | ?{$_.Name -match $filter}
    $list
}

if($file -and (Test-Path $file))
{
    $servers = cat $file
}
if($input)
{
    $servers = $input
}

if($server){$servers += $server}

foreach($srv in $servers)
{
    if(Ping-Server $srv)
    {
        $hotfixes = Check-CitrixHotfix $srv
        $myobj = "" | Select-Object ServerName,Hotfixes,RawObjects
        $myobj.ServerName = $srv
        $myobj.Hotfixes   =  $hotfixes | %{$_.Name}
        $myobj.RawObjects = $hotfixes
        $myobj
    }
    else
    {
        write-host $srv
        write-host "————"
        write-host "Server not pingable"
    }
}

Test-Port (kinda like portqry without verbose output)

We had a little dicussion on www.powershelllive.com forums about the most efficient way to Test a machine before trying a WMI query against it (as it has a log timeout.) My first suggestion was to use a ping (WMI style) but Jeff from http://blog.sapien.com brought up a valid point… what if ICMP is NOT Allowed…

Enter Test-Port. This nifty little script uses the TCPClient Class to test connectivity. Stay tuned as I am planning some mods.

Test-Port
- Takes parameter $srv for Server Name
- Takes Parameter for Port, Defaults to 135 for RPC mapper.
- Takes Timeout.. defaults to 3000 (miliseconds)
- If it cannot connect within timeout… Returns $false
- If it gets exception connecting to port… Returns $false
- If it connects… Returns $True

function Test-Port{
    Param([string]$srv,$port=135,$timeout=3000,[switch]$verbose)
    $ErrorActionPreference = "SilentlyContinue"
    $tcpclient = new-Object system.Net.Sockets.TcpClient
    $iar = $tcpclient.BeginConnect($srv,$port,$null,$null)
    $wait = $iar.AsyncWaitHandle.WaitOne($timeout,$false)
    if(!$wait)
    {
        $tcpclient.Close()
        if($verbose){Write-Host "Connection Timeout"}
        Return $false
    }
    else
    {
        $error.Clear()
        $tcpclient.EndConnect($iar) | out-Null
        if($error[0]){if($verbose){write-host $error[0]};$failed = $true}
        $tcpclient.Close()
    }
    if($failed){return $false}else{return $true}
}

Run-Command.ps1 : Run External Commands with Power!

I was working late tonight and we had to run a bunch of third party EXEs and such. We do this a good bit so I can’t always avoid calling external executables and I also find psexec.exe much easier than any powershell way to run remote commands. That said I find myself constantly writing this.

$servers = Get-Content $file
foreach($server in $servers)
{
   Do Something Here Like
   psexec \\$server mycmd.exe param1
}

I decided to write a script call Run-Command.ps1.
This will take three parameters
- File (list of servers to process)
- Cmd (Command to run with %S% where you want the server name to be replaced)
- Check (just shows what command would run)
- Will also take Piped Input

Example:
PS> .\Run-Command.ps1 -file c:\serverlist.txt -cmd “psexec \\%S% mycmd.exe Hello World” -check

Run-Command.ps1

Param($file,$cmd,[switch]$whatif,[switch]$verbose)
Begin{
    function Ping-Server {
        Param([string]$srv)
        $pingresult = Get-WmiObject win32_pingstatus -f "address=’$srv’"
        if($pingresult.statuscode -eq 0) {$true} else {$false}
    }
    $servers = @()
}
Process{
    if($_)
    {
        if($_.ServerName){$servers += $_.ServerName}
        else{$servers += $_}
    }
}
End{
    if($file){Get-Content $file | %{$servers += $_}}
    foreach($server in $servers)
    {
        if(ping-server $server)
        {
            if($verbose){Write-Host "+ Processing Server $Server"}
            $mycmd = $cmd -replace "\%S\%",$Server
            if($whatif){Write-Host "  - WOULD RUN $mycmd"}
            else{if($verbose){Write-Host "  - Running $mycmd"};invoke-Expression $mycmd}
        }
        else
        {
            Write-Host "+ $Server FAILED PING" -foregroundcolor RED
        }
    }
}

C# to PowerShell Translation Thought Process

A gentleman on the powershell news group the other day was asking about executing SQL stored procedures and I provided him with example that I had posted earlier in my blog (Click Here) While I’m not sure my answer was exactly what he was looking for he was a little curious as to how I went about translating the original C# code. To be clear I am not a coder nor do I truly know C#, but I know enough to translate. Anyway, I decided this would be a good idea for a post

So… Here we GO!

First… I used this as the C# example

http://www.csharp-station.com/Tutorials/AdoDotNet/Lesson07.aspx

I will show you my thought process by section I will include my comments and after I will post Both code Sections

This was fairly simple. In C# you have the ability to take namespace shortcuts by using ‘using ;’ In PowerShell we dont as of yet have that ability so I had to figure out what class SqlConnection was. A MSDN query returned System.Data.SqlClient.SqlConnection. The resulting PowerShell code is

The C# Code:

// Setup
conn = new SqlConnection("Server=(local);DataBase=Northwind;Integrated Security=SSPI");
conn.Open();

The Powershell Code:

$srv = "srv1"
$db = "Northwind"
$conn = new-Object System.Data.SqlClient.SqlConnection("Server=$srv;DataBase=$db;IntegratedSecurity=SSPI")
$conn.Open() | out-null # The out-null is because the method returns a value and I dont want that output

Again… I had to find out what SqlCommand was referencing so… back to MSDN… System.Data.SqlClient.SqlCommand. BTW… I think I should take time now to tell you it is a REALLY GOOD idea to get use to the idea of constructors (how the object should be created) and how to use MSDN to determine the correct way to create an instance of the class/object. It really helps to know what a class is expecting. In this example its good to know the constructor is wanting a string of the SP and A connection OBJECT to use.

The C# Code:

// 1.  create a command object identifying
//     the stored procedure
SqlCommand cmd  = new SqlCommand("CustOrderHist", conn);

Here is the PowerShell Code:

$cmd = new-Object System.Data.SqlClient.SqlCommand("CustOrderHist", $conn)

Here was the tricky part (at least sorta.) From the C# code its not clear if the CommandType.StoredProcedure is a property and it turns out its not. It is an enumeration. It took me a few clicks to figure it out. The first clue was when looking for CommandType… I got an enum and it turns out the valid options was StoredProcedure, TableDirect, or Text (MSDN LINK) Clearly this was an enum, but I was a little unsure how do to enums in powershell. I found this blog by /\/\0\/\/ that helped a lot (/\/\o\/\/ Link)

The C# Code:

// 2. set the command object so it knows
//    to execute a stored procedure
cmd.CommandType = CommandType.StoredProcedure;

I ended up with this Powershell Line:

$cmd.CommandType = [System.Data.CommandType]‘StoredProcedure’

This was fairly simple as well… Just had to drop the new sqlparameter because strongly typing was not required. Again the out-null was because the method returns data I did not want as well as set the parameters.

The C# Code:

// 3. add parameter to command, which
//    will be passed to the stored procedure
cmd.Parameters.Add(new SqlParameter("@CustomerID", custId));

The PowerShell Code:

$cmd.Parameters.Add("@CustomerID","ANATR") | out-Null

These two parts are really just the excution of the the previous code. I think the only difference is the way PowerShell Writes output

The C# Code:

// execute the command
rdr = cmd.ExecuteReader();

// iterate through results, printing each to console
while (rdr.Read())
{
 Console.WriteLine(
 "Product: {0,-35} Total: {1,2}",
 rdr["ProductName"],
 rdr["Total"]);
}

The Powershell Code:

$rdr = $cmd.ExecuteReader()
While($rdr.Read()){
    Write-Host "Product Name: " $rdr[‘ProductName’]
    Write-Host "Total: " $rdr[‘Total’]
}

Here is the Complete PowerShell Code.

$srv = "srv1"
$db = "Northwind"
$conn = new-Object System.Data.SqlClient.SqlConnection("Server=$srv1;DataBase=$db;Integrated Security=SSPI")
$conn.Open() | out-null
$cmd = new-Object System.Data.SqlClient.SqlCommand("CustOrderHist", $conn)
$cmd.CommandType = [System.Data.CommandType]‘StoredProcedure’
$cmd.Parameters.Add("@CustomerID","ANATR") | out-Null
$rdr = $cmd.ExecuteReader()
While($rdr.Read()){
    Write-Host "Product Name: " $rdr[‘ProductName’]
    Write-Host "Total: " $rdr[‘Total’]
}
$conn.Close()
$rdr.Close()

UPDATED!!! Get-Uptime (The Custom Object extravaganza!!!)

One of the most wonderful things about PowerShell is the ability to pass objects down the Pipe for further processing. In my first version of Get-Uptime I did not utilize this because I wanted to show what a huge difference it makes. I wanted to get this out pretty quick so I didn’t add the prettiness that I had in my first version, but it is easy enough to add.

Here is the new Code. After the code I give some examples of how to use it.

Function Get-Uptime{
    Param([string]$server)
    Begin {
        function PingServer {
            Param([string]$srv)
            $pingresult = Get-WmiObject win32_pingstatus -f "address=’$srv’"
            if($pingresult.statuscode -eq 0) {$true} else {$false}
        }
        function myUptime {
            param([string]$srv)
            $os = Get-WmiObject Win32_OperatingSystem -ComputerName $srv
            $uptime = $os.LastBootUpTime
            return $uptime
        }
        function ConvertDate {
            param([string]$date,[string]$srv)
            $year = $date.substring(0,4)
            $Month = $date.Substring(4,2)
            $day = $date.Substring(6,2)
            $hour = $date.Substring(8,2)
            $min = $date.Substring(10,2)
            $sec = $date.Substring(12,2)
            $RebootTime = new-Object System.DateTime($year,$month,$day,$hour,$min,$sec)
            $now = [System.DateTime]::Now
            $uptime = $now.Subtract($RebootTime)
            $uptimeval = "$($uptime.days) days, $($uptime.Hours) hours, $($uptime.Minutes) minutes, $($uptime.seconds) seconds"
            $lastReboot = $rebootTime.toString()
            $sObject = new-Object -typename System.Object
            $sObject | add-Member -memberType noteProperty -name ServerName -Value $srv
            $sObject | add-Member -memberType noteProperty -name Days -Value $uptime.days
            $sObject | add-Member -memberType noteProperty -name Hours -Value $uptime.Hours
            $sObject | add-Member -memberType noteProperty -name Minutes -Value $uptime.Minutes
            $sObject | add-Member -memberType noteProperty -name Seconds -Value $uptime.seconds
            $sObject | add-Member -memberType noteProperty -name uptime -Value $uptimeval
            $sObject | add-Member -memberType noteProperty -name LastReboot -Value $rebootTime.ToUniversalTime()
            $sObject | add-Member -memberType noteProperty -name LastRebootUtc -Value $rebootTime.ToFileTimeUtc()
            write-Output $sObject
        }
        Write-Host
        $process = @()
        $objCollection = @()
    }
    Process {
        if($_){
            if($_.ServerName ){
                $process += $_.ServerName
            }
            else{
                $process += $_
            }
        }
    }
    End {
        if($Server){$process += $server}
        $i = 1
        foreach ($Server in $process){
            write-progress $Server "Total Progress->" -percentcomplete ($i/$process.length*100)
            if(PingServer $server){
                $result = myUptime $server
                $srvObject = ConvertDate $result $server
                $objCollection += $srvObject
            }
            else {
                Write-Host "Server [$server] not Pingable" -foregroundcolor red
            }
            $i = $i+1
        }
        Write-Output $objCollection
        Write-Host
    }
}

Examples:

This get uptime on a Single Server

get-uptime server | %{$_.uptime}

This gets all servers up for more than 30 days

$sl | Get-Uptime | ?{$_.Days -gt 30} | %{write-host "$($_.ServerName) :: $($_.uptime)"}

This Displays the Last Reboot Time of a list of servers.

$sl | get-uptime | %{Write-Host "Server $($_.ServerName) rebooted on $($_.LastReboot)"}

The part of this I want to focus on is this

$sObject = new-Object -typename System.Object
 $sObject | add-Member -memberType noteProperty -name ServerName -Value $srv
 $sObject | add-Member -memberType noteProperty -name Days -Value $uptime.days
 $sObject | add-Member -memberType noteProperty -name Hours -Value $uptime.Hours
 $sObject | add-Member -memberType noteProperty -name Minutes -Value $uptime.Minutes
 $sObject | add-Member -memberType noteProperty -name Seconds -Value $uptime.seconds
 $sObject | add-Member -memberType noteProperty -name uptime -Value $uptimeval
 $sObject | add-Member -memberType noteProperty -name LastReboot -Value $rebootTime.ToUniversalTime()
 $sObject | add-Member -memberType noteProperty -name LastRebootUtc -Value $rebootTime.ToFileTimeUtc()

This is where I define my custom object. There are numerous ways to do this, but I chose this way. Notice the use of add-member cmdlets… It is extremely powerfull and extremely easy to use.

Basically what I do is create a Generic Object $sObject. Add some noteProperties and populate them. Very simple, but as you can very “POWER”ful.

Lee Holms has some excelent information on Custom Objects here:

http://www.leeholmes.com/blog/AddCustomMethodsAndPropertiesToTypesInPowerShell.aspx

I hope you enjoyed this new version… I currently actually keep both versions in my functions.ps1 file that I load in my profile. One as Get-Uptime and one as Get-UptimeExt. I get pretty and Smart :)