Forum Discussion
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) <= 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
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) <= 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
- Alan2022Iron Contributor
dmarquesgn
Try this one since get-jobs can return result of every job.
https://techcommunity.microsoft.com/t5/windows-powershell/powershell-multithreading-using-threadjobs/m-p/3670617#M5601 - LainRobertsonSilver Contributor
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) <= 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
- dmarquesgnIron ContributorHi,
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