Forum Discussion

dmarquesgn's avatar
dmarquesgn
Iron Contributor
Nov 09, 2022

Powershell return variable from within a Invoke-Command

I'm developing a powershell script (and kind of new to it) to go to a couple of servers and extract the RDP logons, so we can check if a certain policy is being followed. So I've search a bit and now I got the script output exatcly as I want. But now I want to send the result over email. But I have a problem, because the variable which is output to console (with the info I need) is inside a Invoke-Command, so I cannot use it after outside the Invoke-Command to send the email. This is my code:

$ServersToCheck=Get-Content "C:\Temp\Servers-RDP2.txt"
foreach ($server in $ServersToCheck) {

    Invoke-Command -ComputerName $server -ErrorAction SilentlyContinue {
    $username = "user"
    $FilterPath = "<QueryList><Query Id='0'><Select Path='Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational'>*[System[(EventID=1149) and TimeCreated[timediff(@SystemTime) &lt;= 604800000]]] and *[UserData[EventXML[@xmlns='Event_NS'][Param1='{0}']]]</Select></Query></QueryList>" -f $username
    $RDPAuths = Get-WinEvent -ErrorAction SilentlyContinue -LogName 'Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational' -FilterXPath $FilterPath
    [xml[]]$xml=$RDPAuths | Foreach { $_.ToXml() }
    $EventData = Foreach ($event in $xml.Event) {
        New-Object PSObject -Property @{
            TimeCreated = (Get-Date ($event.System.TimeCreated.SystemTime) -Format 'dd-MM-yyyy hh:mm:ss')
            User = $event.UserData.EventXML.Param1
            Domain = $event.UserData.EventXML.Param2
            Client = $event.UserData.EventXML.Param3
            Server = hostname
        }
    } $EventData | FT
    }
}

So, I need to use $EventData outside the Invoke-Command so I can add the results of all servers and then send it over by email. How can I use that variable outside the Invoke-Command?

Thanks

  • dmarquesgn 

     

    There's a bit of double-handling going on but the biggest issue is on line 17, where you're taking the data and converting it into a table on the server.

     

    Using Format-Table prevents you from seeing the data as objects, which is what renders the output from Invoke-Command basically unusable.

     

    Here's a simpler version that treats the data as data, not a table. Once the data is back on the calling client, then you can go crazy with things like Format-Table if you like, but the important thing is that because the data is still in a usable (almost-native) state, you can do anything you like with it.

     

    Here's a basic version of the important part: fetching the data.

     

    $RemoteData = Invoke-Command -UseSSL -ComputerName (Get-Content "D:\Data\Servers-RDP2.txt") -ScriptBlock {
        $Username = "lain.robertson"
        $FilterPath = "<QueryList><Query Id='0'><Select Path='Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational'>*[System[(EventID=1149) and TimeCreated[timediff(@SystemTime) &lt;= 604800000]]] and *[UserData[EventXML[@xmlns='Event_NS'][Param1='$Username']]]</Select></Query></QueryList>"
    
        Get-WinEvent -ErrorAction SilentlyContinue -LogName 'Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational' -FilterXPath $FilterPath |
            ForEach-Object {
                [PSCustomObject] @{
                    TimeCreated = $_.TimeCreated.ToString("dd-MM-yyyy HH:mm:ss");
                    User = $_.Properties[0].Value;
                    Domain = $_.Properties[1].Value;
                    Client = $_.Properties[2].Value;
                    Server = $env:COMPUTERNAME;
                }
            }
    }

     

    Here, the remote data is held in a client-side variable named $RemoteData, which is what you can then use as you see fit, including displaying as a table.

     

    $RemoteData | Select-Object -Property * -ExcludeProperty PSComputerName, RunspaceId | Sort-Object -Property Server, Domain, User | Format-Table -AutoSize;

     

    Note: While this is more efficient than the original, it's only an example and I would generally discourage storing large data sets in variables (i.e. $RemoteData in this example). But if your list of servers isn't particularly large then diving into efficiency isn't going to matter.

     

     

    Another example of using $RemoteData to produce a very simple HTML document instead (which you could use as your e-mail body, for example):

     

    $RemoteData | Select-Object -Property * -ExcludeProperty PSComputerName, PSShowComputerName, RunspaceId | Sort-Object -Property Server, Domain, User | ConvertTo-Html -Title "RDP logons";

     

     

    I was also rather curious that you're only searching for one specific username in the event logs, but I just ran with the assumption that was deliberate.

     

    Cheers,

    Lain

  • LainRobertson's avatar
    LainRobertson
    Silver Contributor

    dmarquesgn 

     

    There's a bit of double-handling going on but the biggest issue is on line 17, where you're taking the data and converting it into a table on the server.

     

    Using Format-Table prevents you from seeing the data as objects, which is what renders the output from Invoke-Command basically unusable.

     

    Here's a simpler version that treats the data as data, not a table. Once the data is back on the calling client, then you can go crazy with things like Format-Table if you like, but the important thing is that because the data is still in a usable (almost-native) state, you can do anything you like with it.

     

    Here's a basic version of the important part: fetching the data.

     

    $RemoteData = Invoke-Command -UseSSL -ComputerName (Get-Content "D:\Data\Servers-RDP2.txt") -ScriptBlock {
        $Username = "lain.robertson"
        $FilterPath = "<QueryList><Query Id='0'><Select Path='Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational'>*[System[(EventID=1149) and TimeCreated[timediff(@SystemTime) &lt;= 604800000]]] and *[UserData[EventXML[@xmlns='Event_NS'][Param1='$Username']]]</Select></Query></QueryList>"
    
        Get-WinEvent -ErrorAction SilentlyContinue -LogName 'Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational' -FilterXPath $FilterPath |
            ForEach-Object {
                [PSCustomObject] @{
                    TimeCreated = $_.TimeCreated.ToString("dd-MM-yyyy HH:mm:ss");
                    User = $_.Properties[0].Value;
                    Domain = $_.Properties[1].Value;
                    Client = $_.Properties[2].Value;
                    Server = $env:COMPUTERNAME;
                }
            }
    }

     

    Here, the remote data is held in a client-side variable named $RemoteData, which is what you can then use as you see fit, including displaying as a table.

     

    $RemoteData | Select-Object -Property * -ExcludeProperty PSComputerName, RunspaceId | Sort-Object -Property Server, Domain, User | Format-Table -AutoSize;

     

    Note: While this is more efficient than the original, it's only an example and I would generally discourage storing large data sets in variables (i.e. $RemoteData in this example). But if your list of servers isn't particularly large then diving into efficiency isn't going to matter.

     

     

    Another example of using $RemoteData to produce a very simple HTML document instead (which you could use as your e-mail body, for example):

     

    $RemoteData | Select-Object -Property * -ExcludeProperty PSComputerName, PSShowComputerName, RunspaceId | Sort-Object -Property Server, Domain, User | ConvertTo-Html -Title "RDP logons";

     

     

    I was also rather curious that you're only searching for one specific username in the event logs, but I just ran with the assumption that was deliberate.

     

    Cheers,

    Lain

    • dmarquesgn's avatar
      dmarquesgn
      Iron Contributor
      Hi,
      Thanks for the great tips.
      I was already able to get it right. Also the HTML conversion is quite nice to send over email.
      Cheers

Resources