0

What I am doing here is generating XML output that PRTG can parse when it runs the script. The script is intended to determine when the last windows update install happened. It works fine for all remote servers, but when it runs on the local machine, it's not getting the correct $value. It ends up being null, rather than an integer. I assume I'm missing something here as to how Invoke-Command works on a local server vs a remote server. Would anyone mind showing me where I made a mistake?

$ErrorActionPreference = "Stop"
#Get a list of servers
$servers = Get-ADComputer -SearchBase 'DC=<removed>,DC=int' -Filter {OperatingSystem -NotLike "Windows Server 2003*"} | Sort Name | Select -ExpandProperty Name
$value = ""
#This is the start of the XML output that PRTG will be parsing when the code runs
Write-Host "<prtg>"
#Loop through all servers and attempt to get a value for last windows update install.
foreach($server in $servers) {
    Write-Host "`t<result>`n`t<channel> $server </channel>"
    try {
        Invoke-Command -ComputerName $server -ScriptBlock  {
            $props = @{
                LastDetect = Get-ItemProperty -Path ‘HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\Results\Detect’ -Name LastSuccessTime | select -ExpandProperty LastSuccessTime
                LastInstall = Get-ItemProperty -Path ‘HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\Results\Install’ -Name LastSuccessTime | select -ExpandProperty LastSuccessTime
                }
            $stringdato = $props.LastInstall;
            $DATO = ([datetime]::ParseExact($stringdato, "yyyy-MM-dd HH:mm:ss", $null))
            $today = (Get-Date)
            $timeout = ($today - $DATO)
            $value = $timeout.Days
        }
    } catch {
        # set value to 999 if there is a problem, for PRTG's error threshold.
        $value = 999
    }
    Write-Host "`t<value>$value</value>"
    Write-Host "`t<CustomUnit>days</CustomUnit>`n`t<LimitMaxError>90</LimitMaxError>`n`t<LimitMaxWarning>60</LimitMaxWarning>`n`t<LimitMode>1</LimitMode>`n`t</result>`n"
}
Write-Host "</prtg>"

2 Answers2

0

First of all, you should not do a Invoke-command for your local system. You can put that validation for the resultant.

Secondly, for invoking locally, you need to add the local system's IP / hostname in the TrustedHosts list for the local system which does not make sense.

I have kept a validation using the wmi-object for Win32_Computersystem in your code which should do the needful.

$ErrorActionPreference = "Stop"
#Get a list of servers
$servers = Get-ADComputer -SearchBase 'DC=<removed>,DC=int' -Filter {OperatingSystem -NotLike "Windows Server 2003*"} | Sort Name | Select -ExpandProperty Name
$value = ""
#This is the start of the XML output that PRTG will be parsing when the code runs
Write-Host "<prtg>"




#Loop through all servers and attempt to get a value for last windows update install.
foreach($server in $servers) {
    Write-Host "`t<result>`n`t<channel> $server </channel>"
    try {

    $LocalNetwork=Get-WmiObject Win32_Computersystem;

    ## IF its a local system, then it will go inside IF
    if($LocalNetwork.Name -eq ($server.trim()))
                {


            $props = @{
                LastDetect = Get-ItemProperty -Path ‘HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\Results\Detect’ -Name LastSuccessTime | select -ExpandProperty LastSuccessTime
                LastInstall = Get-ItemProperty -Path ‘HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\Results\Install’ -Name LastSuccessTime | select -ExpandProperty LastSuccessTime
                }
            $stringdato = $props.LastInstall;
            $DATO = ([datetime]::ParseExact($stringdato, "yyyy-MM-dd HH:mm:ss", $null))
            $today = (Get-Date)
            $timeout = ($today - $DATO)
            $value = $timeout.Days



                }
    ## If its a remote system, it will go inside else and will do invoke
      else {
        Invoke-Command -ComputerName $server -ScriptBlock  {
            $props = @{
                LastDetect = Get-ItemProperty -Path ‘HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\Results\Detect’ -Name LastSuccessTime | select -ExpandProperty LastSuccessTime
                LastInstall = Get-ItemProperty -Path ‘HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\Results\Install’ -Name LastSuccessTime | select -ExpandProperty LastSuccessTime
                }
            $stringdato = $props.LastInstall;
            $DATO = ([datetime]::ParseExact($stringdato, "yyyy-MM-dd HH:mm:ss", $null))
            $today = (Get-Date)
            $timeout = ($today - $DATO)
            $value = $timeout.Days
        }
      }
    } 

    catch {
        # set value to 999 if there is a problem, for PRTG's error threshold.
        $value = 999
    }
    Write-Host "`t<value>$value</value>"
    Write-Host "`t<CustomUnit>days</CustomUnit>`n`t<LimitMaxError>90</LimitMaxError>`n`t<LimitMaxWarning>60</LimitMaxWarning>`n`t<LimitMode>1</LimitMode>`n`t</result>`n"

}
Write-Host "</prtg>"

Note: I have not gone through each step, I just kept the validation inside foreach loop. Hope it helps.

Ranadip Dutta
  • 8,857
  • 3
  • 29
  • 45
  • 1
    There's nothing wrong with remoting into the local system as a concept. There may be situations where it doesn't make sense but there's nothing specific about remoting into the local machine that is bad. There are also situations where it makes a lot of sense. – briantist Feb 23 '17 at 15:34
  • @briantist: If it is there , then you have to add the local system to the TrustedHosts list and also I believe a validation should help. – Ranadip Dutta Feb 23 '17 at 15:51
  • 1
    That's not necessarily true. Remoting need to be enabled, but not trusted hosts in a domain environment. – briantist Feb 23 '17 at 15:53
  • 1
    PSRemoting and the localhost or the 127.0.0.1 or the actual IP or the hostname has to be there. Else during the Invoke it will throw the error for the same. I have faced it many times. Anyways, my best preference is to validate it if its local then dont invoke with computer name as a parameter else invoke with computername. Let me know if the validation methodology is wrong or not whatever I have implemented in the answer. – Ranadip Dutta Feb 23 '17 at 16:01
  • Are you in a domain environment? I have nothing in trusted hosts and I can remote to the local computer using a `-ComputerName` of any of the following: `localhost`, `127.0.0.1`, `.`, the computer's hostname, the computer's FQDN. All of that works. Again, I agree that if you're running something against the local computer that works without remoting, you shouldn't use it, but there are other use cases where it makes sense and trusted hosts is not required. – briantist Feb 23 '17 at 16:16
  • Yes, I am in a domain. FQDN works only in my case if you are not adding the localhost or 127.0.0.1 because my host file is not having any entry for them to get resolved. Everything is coming from the DNS server. if the DNS records are present then we are good to go. But yes, I second you . It should work.Edge case scenario is to handle it with the validation – Ranadip Dutta Feb 23 '17 at 16:24
  • For what it's worth, this is a domain and we have a GPO that enables winrm and sets the trusted hosts to the entire range of subnets in the domain. – Peter Bollwerk Feb 23 '17 at 22:53
  • @PeterBollwerk: No, I do not wish to touch GPO for this thing. Because, I do not wish to group certain set of computers for this. – Ranadip Dutta Feb 24 '17 at 04:55
0

Add $value as last statement of the ScriptBlock of the Invoke-Command cmdlet. The value of $value should be stored in $result.

 ...
 $result = Invoke-Command -ComputerName $server -ScriptBlock  {
        ...
        $timeout = ($today - $DATO)
        $value = $timeout.Days
        $value   # <---- ADD THIS LINE
        ...

I think it has to do with the serialization process of PowerShell remoting (maybe it "auto-serializes" the last variable that was assigned). "Normally" if you want to return a value from a PowerShell scriptblock you've to call the variable like "$var". Therefore the content of $var is sent down the pipeline. See the answer of this thread for details.

As an alternative you can also add following lines to your script:

 return $value

OR:

 $value  # <----- sent the content of $value down the pipeline
 return

Hope that helps

Community
  • 1
  • 1
Moerwald
  • 10,448
  • 9
  • 43
  • 83
  • Can you explain why it is auto-serialization ? He is getting it for all the remote systems and only for local it is not. May be PS remoting is disabled in local or may be the local system is not added in the TrustedHosts list. test-wsman should help in that case. Please elaborate your point – Ranadip Dutta Feb 23 '17 at 16:05
  • I appreciate the suggestions, but they print out the $value, rather than passing the contents of $value to the main script scope. This results in a misformed XML output, where the contents are not inside the XML tag Maybe I need to move the Write-Host "`t$value" line into the Invoke-Command script block. – Peter Bollwerk Feb 23 '17 at 18:26
  • @Bollwerk: I've updated my answer. You've to store the output of `Invoke-Command` -> my post now store the output in $result. Check if the value is included in `$result`. – Moerwald Feb 24 '17 at 14:35