Revisiting Test-Port using a PowerShell workflow

I’ve been using the Test-Port code provided by Boe Prox that you can find in the script gallery on this page: http://gallery.technet.microsoft.com/scriptcenter/97119ed6-6fb2-446d-98d8-32d823867131

Instead of testing first if a computer replies to an ICMP request and then testing with the built-in cmdlet Test-WSMan if the powershell remoting is working, I prefer to kill two birds with one stone. Before showing how, let me mention a few interesting links on this topic:

I prefer to test if the TCP port 5985 (default PS remoting port) is opened because you can actually reduce the timeout when it fails.
And, as I previously said, when it succeeds, it also means that a remote computer is present on the network and that its firewall configuration allows you to reach its WinRM (Windows Remote Management) service.

Workflow Test-TCPPort {            
 param(            
    [parameter(mandatory)]            
    [string[]]$ComputerName,            
            
    [parameter()]            
    [ValidateRange(1,65535)]            
    [int]$Timeout=1000,            
            
    [parameter()]            
    [ValidateRange(1,65535)]            
    [int]$Port=5985            
)            
    ForEach -parallel ($Computer in $ComputerName){            
        Sequence {            
            $obj = Inlinescript {            
            
                #Reset             
                $Open = $false            
            
                #Create object for connecting to port on computer              
                $tcpobject = New-Object -TypeName System.Net.Sockets.TcpClient              
                                
                #Connect to remote machine's port                            
                $connect = $tcpobject.BeginConnect($using:Computer,$using:Port,$null,$null)              
            
                #Configure a timeout before quitting              
                $wait = $connect.AsyncWaitHandle.WaitOne($using:Timeout,$false)              
                                
                #If timeout              
                If(!$wait)            
                {              
                    $tcpobject.Close()              
                    $Result = "Timed Out"              
                } Else {              
                    try {            
                        $tcpobject.EndConnect($connect) | out-Null              
                        $Open = $true            
                        $Result = 'Success'            
                    } catch {            
                        $Result = "Error"            
                    }            
                    $tcpobject.Close()              
                }            
                New-Object -TypeName PSObject -Property @{            
                    Computer = $using:Computer            
                    Port = $using:Port            
                    Opened = $Open            
                    Result = $Result            
                }            
            }            
            $obj            
        }            
    }            
} # end of workflow

NB: the timeout value represents milliseconds. I don’t remember why I’ve set an upper limit. Anyway 65585 milliseconds equals to 65.585 seconds which should be enough in most use cases of this workflow.
PS: Powershell workflows have been introduced in Powershell Version 3.0 -> Get the ‘Windows Management Framework 3.0’

Disable/Enable task triggers

On either Powershell V2 or V3 on a Windows 7 workstation, I’ve already presented what can be done with scheduled tasks using Com objects in the following previous posts:

Now, let’s take a specific example: the case of the Disk Defragmentation schedule

  • Display the current status of the task
  • $TaskService = New-Object -com schedule.service            
    $TaskService.Connect($env:COMPUTERNAME)            
     $TaskService.GetFolder(            
      '\Microsoft\Windows\Defrag').            
      GetTasks(1) |             
      Select Name,Path,Enabled |             
      ft -AutoSize
  • Disable the task
  • If I disable the task like this:

    # Disable the task            
    $TaskService.GetFolder(            
     '\Microsoft\Windows\Defrag').            
     GetTask('ScheduledDefrag').Enabled = $false            
    

    We get the following message from the UI, whenever I use the Tools tab in the properties of a volume.
    Defrag Task Disabled

  • Disable the Defrag task using the UI
  • If I untick the ‘Run on a schedule’ property in the schedule configuration options, we have the following:
    Defrag Task Trigger Turned Off
    The task isn’t actually disabled but only its schedule has been turned off
    Defrag task trigger turned off in mmc

  • Disable the Defrag task triggers
  • Just disabling the triggers can actually be achieved using the task registration method with a specific flag.
    task registration flags
    Source: http://msdn.microsoft.com/en-us/library/windows/desktop/aa382538%28v=vs.85%29.aspx

    $TaskService = New-Object -com schedule.service            
    $TaskService.Connect($env:COMPUTERNAME)            
    $taskDef = $TaskService.            
     GetFolder('\Microsoft\Windows\Defrag').            
      GetTask('ScheduledDefrag').Definition            
    $taskDef.Triggers|foreach-object{$_.Enabled=$false}            
    # Define a constant             
    # $TASK_CREATE_OR_UPDATE_FLAG = 0x6            
    $TASK_UPDATE_FLAG = 0x4            
    $TaskService.GetFolder('\Microsoft\Windows\Defrag').            
     RegisterTaskDefinition(            
      'ScheduledDefrag',            
      $taskDef,            
      $TASK_UPDATE_FLAG,            
      $null,            
      $null,            
      $taskDef.Principal.LogonType            
      )

Delete elements from an array

I was working on all workstations in a domain and simply building an array with computer names like this

$computersar = @()            
Get-ADComputer -ResultPageSize 10000 -SearchScope Subtree -SearchBase (Get-ADDomain).DistinguishedName -Filter {            
    (OperatingSystem -Like "Windows*7*") -or (OperatingSystem -Like "Windows*XP*")} | ForEach-Object -Process {             
        $computersar += $_.Name            
}

Then I wanted to remove some computers from this array. I’ve tried using the ‘Remove’ method but it failed.
Exception calling “Remove” with “1” argument(s): “Collection was of a fixed size.”

The error message is quite explicit, but this doesn’t explain why there’s a ‘remove’ method.
To discover why, here’s what I did:

# Initialize an empty array            
$array = @()            
# Build a messy array (unsorted with my computername in the middle)            
0..5 | % { $array += 'Test{0:000}' -f ([int]$_) }            
10..15 | % { $array += 'Test{0:000}' -f ([int]$_) }            
$array += $env:computername            
6..9 | % { $array += 'Test{0:000}' -f ([int]$_) }            
16..19 | % { $array += 'Test{0:000}' -f ([int]$_) }            
            
# Display the content            
$array            
            
# Make sure that we have 21 records in the array            
$array.Count             
            
# Use the new -in operator to check that my computername is found in the array            
"$env:computername" -in $array            
            
# Get its index position in the array (12 actually)            
$array.IndexOf("$env:computername")            
            
# Check what type of object we have            
$array.GetType()            
            
# It seems that there's a method named 'remove', let's see how to use it            
$array | gm -MemberType Method | ? Name -eq 'Remove' | Select -ExpandProperty Definition            
            
# Use the 'remove' method to exfiltrate my computername from the array            
try {            
    $array.Remove($array.IndexOf("$env:computername"))            
} catch {            
    $_.Exception.Message            
}            
# Exception calling "Remove" with "1" argument(s): "Collection was of a fixed size."            
            
# As you see the remove method doesn't work            
            
# Let's examine the base array object            
[system.array]| gm -Static            
            
# Check the difference with $array            
$array | gm -Static            
            
# this statement will return true            
$array -is [system.array]            
$array -is [system.object]            
# because it's referenced as type            
$array.psobject.TypeNames            
            
# The array has also a property that indicates that it has a fixed size            
$array.IsFixedSize            
            
# It has a 'Clear' method, that would help that doesn't exist on the $array object            
[system.array]| gm -static | ? Name -eq 'Clear' | Select -ExpandProperty Definition            
            
# Let's try it:            
[system.array]::Clear($array,$array.IndexOf("$env:computername"),1)            
            
# Now this statement will return false             
"$env:computername" -in $array            
            
# Really make sure that the value at index 12 is cleared            
0..($array.Count-1) | % { '{0} -> --{1}--' -f $_,$array[$_]}            
            
# The array size is fixed and hasn't changed            
$array.Count            
            
# If we iterate through the array, we may encounter the empty value we left at index 12            
            
# Wait, we can do better than this            
            
# Step 1: sort the array, so that empty values are located starting at index 0            
[system.array]::Sort($array)            
0..($array.Count-1) | % { '{0} -> --{1}--' -f $_,$array[$_]}            
            
# Step2: reverse the order so that empty values are located at the end of the array            
[system.array]::Reverse($array)            
0..($array.Count-1) | % { '{0} -> --{1}--' -f $_,$array[$_]}            
            
# Step3: resize the array to strip empty values at the end of the array            
[system.array]::Resize([ref]$array,($array.Count-1))            
            
# Make sure the array count is 20            
$array.Count            
            
# Get the content of the array            
0..($array.Count-1) | % { '{0} -> --{1}--' -f $_,$array[$_]}            
            
# We have finally been able to remove a string in the middle of the array            
            
# There's another type of arrays            
$arlist = New-Object System.Collections.ArrayList            
            
# Let's fill the array with the sames values            
0..5 | % { $arlist.Add('Test{0:000}' -f ([int]$_)) | Out-Null}            
10..15 | % { $arlist.Add('Test{0:000}' -f ([int]$_)) | Out-Null}            
$arlist.Add($env:computername) | Out-Null            
6..9 | % { $arlist.Add('Test{0:000}' -f ([int]$_)) | Out-Null}            
16..19 | % { $arlist.Add('Test{0:000}' -f ([int]$_)) | Out-Null}            
            
# Display its content            
$arlist            
            
# Make sure that we have 21 records in the array            
$arlist.Count             
            
# Use the new -in operator to check that my computername is found in the array            
"$env:computername" -in $arlist            
            
# Get its index position in the array (12 actually)            
$arlist.IndexOf("$env:computername")            
            
# Check what type of object we have            
$arlist.GetType()            
$arlist -is [system.array]            
$arlist -is [system.object]            
$arlist.psobject.TypeNames            
            
# Does it have a fixed size ?            
$arlist.IsFixedSize            
            
# See again what's in the array before            
0..($arlist.Count-1) | % { '{0} -> --{1}--' -f $_,$arlist[$_]}            
            
# Remove my computer from the array (using the Remove method)            
$arlist.Remove($env:computername)            
            
# See again what's in the array afterward            
0..($arlist.Count-1) | % { '{0} -> --{1}--' -f $_,$arlist[$_]}            
            
# Get the count            
$arlist.Count

I came across this forum post that also sheds some lights on why the ‘remove’ method exists and failed.

http://social.technet.microsoft.com/Forums/en-US/winserverpowershell/thread/c13ea9bd-dac4-44be-99f7-272a8eb0c55d/

Bonus: I could have used the Select-String cmdlet and simply do:

$array |            
 select-String -Pattern "$env:computername" -NotMatch|            
  % { '--{0}--' -f $_}

We end up with a new array and its count is 20

$newar = $array | select-String -Pattern "$env:computername" -NotMatch            
$newar.Count

Troubleshooting Object reference not set to an instance of an object message

  • Problem description / Symptoms
Invoke-Command -ComputerName RemotePC -FilePath e:\myfile.ps1 -Verbose

Object reference not set to an instance of an object.
+ CategoryInfo : OpenError: (RemotePC:String) [], RemoteException
+ FullyQualifiedErrorId : PSSessionStateBroken

Enter-PSSession -ComputerName RemotePC

Enter-PSSession : Object reference not set to an instance of an object.
At line:1 char:2
+ Enter-PSSession -ComputerName RemotePC
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Enter-PSSession], NullReferenceException
+ FullyQualifiedErrorId : RemoteRunspaceStateInfoReason

  • Prerequisites:

If you haven’t done it yet and if you want to understand how Remoting works in Powershell, you absolutely need to read the free Secrets of Powershell Remoting ebook written by Don Jones and Tobias Weltner. There’s a whole chapter on Diagnotics and Troubleshooting and a diagram on the elements and components of PowerShell Remoting on page 9 (figure 1.1)

  • Troubleshooting steps
  1. Check the WSMan protocol
  2. First we use Test-WSMan to test if the WSMan protocol, the transport protocol over HTTP on TCP port 5985 (by default), being used is working properly.

    Test-WSMan -ComputerName RemotePC

    This step validates the fact that the firewall is opened on the remote PC and that the listener on the remote PC works.
    WSMan

  3. Check the authentication performed over the WSMan protocol
  4. Then in a domain environment we test if the Authentication is working properly

    Test-WSMan -ComputerName RemotePC -Authentication Kerberos

    WSMan authentication

  5. Check the WSMan listener on the remote computer
  6. On the remotePC we verify the WSMan listener settings:

    dir WSMan:\localhost\Listener            
    dir WSMan:\localhost\Listener\Listener_641507880

    WSMan Listener

  7. Check the WinRM service on the remote computer
  8. On the remotePC we verify the WinRM service is running.

    Get-Service -Name WinRM |             
     Stop-Service -Force -PassThru |             
     Start-Service -Verbose

    WinRM

  9. Check the endpoint on the remote computer
  10. Get-PSSessionConfiguration            
    Get-PSSessionConfiguration -Name microsoft.powershell | fl -p *

    Endpoint

    Before switching to advanced troubleshooting mode, we run the following additional steps:

  11. Restart the computer
  12. Restart-Computer -ComputerName RemotePC -Force -wait -For:WinRM
  13. Connect with another user
  14. Invoke-Command -ComputerName RemotePC -Credential (            
    Get-Credential DomainName\UserName) -ScriptBlock {            
     Write-Host "Test"            
    }

    NB: In my case, invoking command as another user works. So I won’t use the advanced troubleshooting steps that you can find the ‘Secrets of Powershell Remoting’ ebook.
    Instead we keep looking at basic things.

  15. Check the eventlog
  • There’s an event ID 1542 in the Application log complaining that it cannot load the file named UsrClass.dat located in AppData\Local\Microsoft\Windows\
    Application eventlog 1
  • Let’s get the exact time and the process ID responsible for logging the above event so that we can use this info to filter a procmon trace.
    Application eventlog 2
    Unfortunately, the procmon trace doesn’t even show that it fails to found the UsrClass.dat.
  • Wait, there’s another event in the ‘Known folders’ log,

    Eventlog Known folders
    We can also see that it cannot find the ‘temporary internet files’ located in AppData\Local\Microsoft\Windows\Temporary Internet Files

  • When I try to open a cmd window ‘running as a different user’, I can’t
    Open cmd via RunAs
  • Cause

It appears actually that the AppData\Local folder is simply missing inside the user profile.

  • Solution

I ended up deleting the user profile and everything turned back to normal.

Configure Outlook Anywhere in Outlook 2010

At the Office, the Exchange guy activated the Outlook Anywhere feature without knowing that it would have an impact on internal Office 2010 clients. He probably failed to read the following warning:
Warning Outlook anywhere
I’ve been tasked to remove the Outlook anywhere configuration being pushed to our internal Oulook 2010 clients.
I’ve first checked group policy objects but there’s only one setting that would disable the access to the UI…
outlook anywhere UI

Then I’ve checked our default outlook configuration profile that was created by the OCT (Office Customization Tool). The following technet page has more info about this topic.

NB: To launch OCT, you just execute the setup.exe of Office with the /admin argument.

However the default profile is being created for new users, it’s set to not replace existing Outlook profile and the Outlook anywhere profile was of course not activated and configured.

Then I’ve found that Microsoft provides an adm template for Office 2007 to deploy Outlook anywhere settings. It can be found on http://support.microsoft.com/kb/961112

Sounds cool but it first requires the hotfix to be installed. There isn’t any for Outlook 2010.
It’s also the old format for GPO templates instead of XML based admx templates (their syntax is case-sensitive).

Instead I’ve fired up the procmon tool and found an old vbscript on this page: http://www.sys-admin.co.uk/?p=94

After few minutes of reverse engineering, I was confident that:

  • I don’t need to use a recusive query of subkeys as I can find the default (active) outlook profile as a string under the HKCU:\Software\Microsoft\Windows NT\CurrentVersion\Windows Messaging Subsystem\Profiles key in the Value named DefaultProfile
  • The “GUID” of the outlook profile parameters is always located under the HKCU:\Software\Microsoft\Windows NT\CurrentVersion\Windows Messaging Subsystem\Profiles\<DefaultProfileName>0a0d020000000000c000000000000046 key in the value named 01023d15
  • Deleting the value 00036623 and closing/reopening Outlook was unticking the following option
  • Outlook Anywhere setting

  • Instead of setting a GPO, I can directly write into HKCU:\Software\Microsoft\Office\14.0\Outlook\RPC

Here’s the code I’ve been using to fix our issue

#Requires -Version 2.0

# Get the default outlook profile being used by default
try {
    $defaultprofile = Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows NT\CurrentVersion\Windows Messaging Subsystem\Profiles" -Name DefaultProfile -ErrorAction Stop
} catch {
    Write-Debug -Message "Cannot find a default outlook profile"
    return
}
if ($defaultprofile) {
    # Get the Outlook profile "GUID"
    try {
        $OLprofile = Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows NT\CurrentVersion\Windows Messaging Subsystem\Profiles\$($defaultprofile.DefaultProfile)\0a0d020000000000c000000000000046"  -Name '01023d15' -ErrorAction Stop
    } catch {
        Write-Debug -Message "Failed to find a specific outlook profile"
        return
    }
    if ($OLprofile) {
        # $($OLprofile.'01023d15') is an array of system.byte
        $hexlocation = -join ($($OLprofile.'01023d15') | ForEach-Object -Process {
            '{0:X2}' -f [int]$_   
        })
        try {
            # Delete 
            Remove-ItemProperty -Path "HKCU:\Software\Microsoft\Windows NT\CurrentVersion\Windows Messaging Subsystem\Profiles\$($defaultprofile.DefaultProfile)\$($hexlocation.ToLower())" -Name '00036623' -Force -ErrorAction Stop 
        } catch {
            Write-Debug -Message "Failed to find the 'Connect to Microsoft Exchange using HTTP' setting in the registry"
            return
        }
        # If we've deleted the above value, we can now grey out the option in the UI:
        try {
            New-Item -Path "HKCU:\Software\Microsoft\Office\14.0\Outlook\RPC" -Force -ErrorAction Stop
            Set-ItemProperty -Path "HKCU:\Software\Microsoft\Office\14.0\Outlook\RPC" -Name enablerpctunnelingui -Value 4 -Force -ErrorAction Stop -Type DWORD
        } catch {
            Write-Debug -Message "Failed to grey out the option in the registry"
        }
    }
 }

Bonus: the URL of the target proxy server for Exchange is located in the 001f6622 value.

Follow-up about Hyper-V VM integration services

Following my post about Hyper-V VM integration services, https://p0w3rsh3ll.wordpress.com/2012/11/12/keeping-hyper-v-vm-integration-services-up-to-date/, I’ve some additional tips to share:

  • A new version of the VM integration services has been released in November: 6.2.9200.16433
  • It was first spotted by Thomas Maurer on the following page http://www.thomasmaurer.ch/2012/11/first-patch-tuesday-with-windows-server-2012-hyper-v-patches-new-version-of-hyper-v-integration-servies/

    The new package is actually bundled in KB2770917

    I couldn’t find the package in the download center as mentioned on the KB2770917
    My WindowsUpdate.log indicates that it got it from this location

  • About identifying VM integration services version
  • You can actually get the VM integration services version only when the VM is running. So, we can just the following filter:

    Get-VM | ? State -eq "Running" |            
     Select Name,State,Integrationservicesversion |            
     ft -AutoSize

    Get VM integration services version

  • About upgrading the VM integration services
    • Load the ISO that contains the new version
    • # Load the ISO            
      Get-VM | ? State -eq "Running" |            
       ?  IntegrationServicesVersion -eq "6.2.9200.16384" |            
       Get-VMDvdDrive | Set-VMDvdDrive -Path C:\Windows\system32\vmguest.iso
    • Run the setup.exe
    • Get-VM | ? State -eq "Running" | ?  IntegrationServicesVersion -eq "6.2.9200.16384" |             
      ForEach-Object -Process {            
          Invoke-Command -ComputerName $_.VMName -ScriptBlock {            
              Get-WmiObject -Query 'Select * FROM win32_volume WHERE DriveType = 5' |            
              ForEach-Object -Process {            
                  if (Test-Path -Path "$($_.Caption)support\$env:PROCESSOR_ARCHITECTURE\setup.exe") {            
                      # 'Found'            
                      $ps = new-object System.Diagnostics.Process            
                      $ps.StartInfo.Filename = "$($_.Caption)support\$env:PROCESSOR_ARCHITECTURE\setup.exe"            
                      $ps.StartInfo.Arguments = " /quiet /norestart"            
                      $ps.StartInfo.RedirectStandardOutput = $True            
                      $ps.StartInfo.UseShellExecute = $false            
                      $ps.start()            
                      $ps.WaitForExit()            
                      'Exit code {0}' -f $ps.ExitCode            
                      Restart-Computer -Force            
                  } else {            
                      # 'Not Found'            
                  }            
              }            
          }            
      }

      Note that a reboot is required and is included in the above code.

  • About troubleshooting the VM integration services upgrade
    • using the log file
    • To troubleshoot the installation of VM integration services, you can read the C:\Windows\vmguestsetup.log located on the guest operating system.

    • using the event log
    • If the upgrade is successful, you’ll find the ‘Setup’ eventlog an entry about KB955484 (don’t be surprised but the page doesn’t exist anymore…)
      VM integration event

    • using the Deployment Image Servicing and Management tool
    • If I do:

      dism --% /online /get-packages

      We can see that:
      DISM

    Bonus: a wiki techNet article about ‘PowerShell – Running Executables’
    http://social.technet.microsoft.com/wiki/contents/articles/7703.powershell-running-executables.aspx

Keeping Hyper-V VM integration services up-to-date

If you’ve enabled the Hyper-V role on a Windows 2012 server and if you host Windows 2008 R2 virtual machines, you should read the following articles about:

You may wonder why it may be a good idea of enabling VM integration services and keeping it up-to-date.
Well, there’s at least an excellent reason if you plan to use the new Replica feature of Hyper-V on windows server 2012:
VM integration services and replica

On a fresh installation of a virtual machines running Windows 2008 R2, I get the following:
VMintegration before

The full ‘StatusDescription’ states that:

{OK, The protocol version of the component installed in the virtual machine does not
match the version expected by the hosting system}

# Load the ISO containing the latest VMintegration package                        
Get-VMDVDdrive -VMName HCN |             
 Set-VMDvdDrive -Path C:\Windows\system32\vmguest.iso                        
                        
# Check that the ISO file has been attached                        
Get-VMDvdDrive -VMName HCN                        
                        
@"
 Now, inside the VM, you need to runn setup.exe located on the DVD drive

 & reboot the VM at the end            
"@            
                        
# Unload the ISO file from the DVD drive                        
Get-VM -Name HCN | Get-VMDvdDrive |             
 Set-VMDvdDrive -Path $null

You can see the result
VMintegration afterward

Of course, you can try to automate it, even if I don’t recommend it as it depends on:

    • the network configuration between the host and the VM
    • the firewall configuration of the VM
    • whether both the host and VM are members of the same domain

You can still have a look to the following links for this purpose: