powershellwindows-serverantiviruswindows-defender

Chaining commands in single PS line results in wonky output


In a modification of this, I'm doing this:

function Get-AntiMalwareStatus {
[CmdletBinding()]
param
(
[Parameter(Position=0,Helpmessage = 'Possible Values: AllServer')]
[ValidateSet('AllServer')]
$Scope
)
$result=@()
$ErrorActionPreference="SilentlyContinue"
switch ($Scope) {
$null {
Get-MpComputerStatus | Select-Object -Property Antivirusenabled,AMServiceEnabled,AntispywareEnabled,BehaviorMonitorEnabled,IoavProtectionEnabled,`
NISEnabled,OnAccessProtectionEnabled,RealTimeProtectionEnabled,AntivirusSignatureLastUpdated
}
AllServer {
$result=@() 
$server="server1","server2","server3"
foreach ($s in $server) {
$rs=Invoke-Command -ComputerName $s {Get-MpComputerStatus | Select-Object -Property Antivirusenabled,AMServiceEnabled,AntispywareEnabled,BehaviorMonitorEnabled,IoavProtectionEnabled,NISEnabled,OnAccessProtectionEnabled,RealTimeProtectionEnabled,AntivirusSignatureLastUpdated,AntispywareSignatureLastUpdated,NISSignatureLastUpdated}
If ($rs) {
$result+=New-Object -TypeName PSObject -Property ([ordered]@{
'Server'=$rs.PSComputername
'Anti-Virus'=$rs.AntivirusEnabled
'AV Update'=$rs.AntivirusSignatureLastUpdated
'Anti-Malware'=$rs.AMServiceEnabled
'Anti-Spyware'=$rs.AntispywareEnabled
'AS Update'=$rs.AntispywareSignatureLastUpdated
'Behavior Monitor'=$rs.BehaviorMonitorEnabled
'Office-Anti-Virus'=$rs.IoavProtectionEnabled
'NIS'=$rs.NISEnabled
'NIS Update'=$rs.NISSignatureLastUpdated
'Access Prot'=$rs.OnAccessProtectionEnabled
'R-T Prot'=$rs.RealTimeProtectionEnabled
})
}
}
}
}
Write-Output $result
}

WHich results in:

PS C:\WINDOWS\system32> Get-AntiMalwareStatus -Scope AllServer | Format-Table -AutoSize

Server          Anti-Virus AV Update             Anti-Malware Anti-Spyware AS Update            Behavior Monitor Office-Anti-Virus  NIS NIS Update          
------          ---------- ---------             ------------ ------------ ---------            ---------------- -----------------  --- ----------          
server1              False 12/31/1969 7:00:00 PM         True         True 8/10/2023 5:37:49 PM             True              True True 8/10/2023 5:37:16 PM
server2              False 12/31/1969 7:00:00 PM         True         True 8/9/2023 2:43:53 PM              True              True True 8/9/2023 2:46:39 PM 
server3              True 8/5/2023 9:44:58 PM           True         True 8/5/2023 9:44:59 PM              True              True True 8/5/2023 9:44:58 PM 

But when I modify line 20 to:

$rs=Invoke-Command -ComputerName $s {Get-MpComputerStatus | Select-Object -Property Antivirusenabled,AMServiceEnabled,AntispywareEnabled,BehaviorMonitorEnabled,IoavProtectionEnabled,NISEnabled,OnAccessProtectionEnabled,RealTimeProtectionEnabled,AntivirusSignatureLastUpdated,AntispywareSignatureLastUpdated,NISSignatureLastUpdated;Get-ComputerInfo | select WindowsProductName}

I get:

PS C:\WINDOWS\system32> Get-AntiMalwareStatus -Scope AllServer | Format-Table -AutoSize

Server                             Anti-Virus     AV Update                      Anti-Malware  Anti-Spyware  AS Update                     Behavior Monitor Office-Anti-Virus NIS           NIS Update                   
------                             ----------     ---------                      ------------  ------------  ---------                     ---------------- ----------------- ---           ----------                   
{server1, server1}                 {False, $null} {12/31/1969 7:00:00 PM, $null} {True, $null} {True, $null} {8/10/2023 5:37:49 PM, $null} {True, $null}    {True, $null}     {True, $null} {8/10/2023 5:37:16 PM, $null}
{server2, server2}                 {False, $null} {12/31/1969 7:00:00 PM, $null} {True, $null} {True, $null} {8/9/2023 2:43:53 PM, $null}  {True, $null}    {True, $null}     {True, $null} {8/9/2023 2:46:39 PM, $null} 
{server3, server3}                 {True, $null}  {8/5/2023 9:44:58 PM, $null}   {True, $null} {True, $null} {8/5/2023 9:44:59 PM, $null}  {True, $null}    {True, $null}     {True, $null} {8/5/2023 9:44:58 PM, $null} 

Also same result when I chain the Get-ComputerInfo | select WindowsProductName to the end of line 13 as well, or just at the end of line 13.

What I'm trying to do is get OS version and AV status together, as I have many 2019 and 2022 servers, and we have a different response for which OS version if AV is not auditing correctly.


Solution

  • With your modification, $rs receives two objects in each iteration, so that property access such as $rs.PSComputername - due to member-access enumeration - analogously yields two values.

    Since the two objects on which the property access is made are of disparate types and don't share properties, the second value in each value pair is $null - this is what you're seeing in the formatted output.

    A minimal repro:

    # The .Foo property receives *two* values, the second one being $null,
    # because the nested [pscustomobject] has no .Name property.
    [pscustomobject] @{ 
      Foo = $(Get-Item /; [pscustomobject] @{ Unrelated=1 }).Name 
    } | Format-Table
    

    Output:

    Foo
    ---
    {/, $null}
    

    As an - inconsequential - aside:


    The solution is to capture these two objects in separate variables, and then combine their properties in the custom object that is constructed for output.

    switch ($Scope) {
      $null {
        Get-MpComputerStatus | Select-Object -Property Antivirusenabled, AMServiceEnabled, AntispywareEnabled, BehaviorMonitorEnabled, IoavProtectionEnabled, `
          NISEnabled, OnAccessProtectionEnabled, RealTimeProtectionEnabled, AntivirusSignatureLastUpdated
      }
      AllServer {
        $server = 'server1', 'server2', 'server3'
        foreach ($s in $server) {
          # COLLECT THE TWO OUTPUT OBJECTS SEPARATELY
          $rs, $prodName = 
            Invoke-Command -ComputerName $s { 
              Get-MpComputerStatus | Select-Object -Property Antivirusenabled, AMServiceEnabled, AntispywareEnabled, BehaviorMonitorEnabled, IoavProtectionEnabled, NISEnabled, OnAccessProtectionEnabled, RealTimeProtectionEnabled, AntivirusSignatureLastUpdated, AntispywareSignatureLastUpdated, NISSignatureLastUpdated 
              Get-ComputerInfo | Select-Object -ExpandProperty WindowsProductName
            }
          If ($rs -and $prodName) {
            [pscustomobject] @{
              'Server'             = $rs.PSComputername
              'WindowsProductName' = $prodName # NEW PROPERTY with the Windows product name.
              'Anti-Virus'         = $rs.AntivirusEnabled
              'AV Update'          = $rs.AntivirusSignatureLastUpdated
              'Anti-Malware'       = $rs.AMServiceEnabled
              'Anti-Spyware'       = $rs.AntispywareEnabled
              'AS Update'          = $rs.AntispywareSignatureLastUpdated
              'Behavior Monitor'   = $rs.BehaviorMonitorEnabled
              'Office-Anti-Virus'  = $rs.IoavProtectionEnabled
              'NIS'                = $rs.NISEnabled
              'NIS Update'         = $rs.NISSignatureLastUpdated
              'Access Prot'        = $rs.OnAccessProtectionEnabled
              'R-T Prot'           = $rs.RealTimeProtectionEnabled
            }
          }
        }
      }
    }