SOLVED

Powershell return variable from within a Invoke-Command

Iron Contributor

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

3 Replies
best response confirmed by dmarquesgn (Iron Contributor)
Solution

@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.

 

LainRobertson_0-1668086503745.png

 

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";

 

LainRobertson_1-1668086816354.png

 

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

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
1 best response

Accepted Solutions
best response confirmed by dmarquesgn (Iron Contributor)
Solution

@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.

 

LainRobertson_0-1668086503745.png

 

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";

 

LainRobertson_1-1668086816354.png

 

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

View solution in original post