Return Object Array from Invoke-Command - powershell

Powershell script is only returning one value from Invoke-Command not all the objects I would expect.
I am adding a property to an existing object on a remote server then returning it back.
$inventory contains application objects that look like this:
Name : Centinel-Dev-AmazonServer
Platform : Centinel
Type : Windows Service
Tier : APP
Status : Running
Name : Portal-QA-Walmart
Platform : Portal
Type : Windows Service
Tier : APP
Status : Running
There are many more application objects similiar to the two given when I run my script it only returns the first object not all.
function verify_workingDir {
param($server)
$inventory = GetInventory -Server $server
$return_object = #()
$return_object += Invoke-Command -ComputerName $server -ScriptBlock {
$inventory = $args[0]
$return_object = #()
foreach ($Application in $inventory) {
$applicationParamters = Get-ItemProperty -Path x:\x\x\x\$($Application.Name)\Parameters
$verify_object = [pscustomobject] #{
WorkingDirectory = $applicationParamters.ServiceWorkingDir
}
$ExpandVerifyObject = $verify_object | Select-Object -Property #{
Name = "MyProperties"
Expression = {$_.WorkingDirectory }
} | Select-Object -ExpandProperty MyProperties
Add-Member -MemberType NoteProperty -Name WorkingDirectory -InputObject $Application -TypeName PSObject -Value $ExpandVerifyObject
$return_object += $Application
}
return $return_object
} -ArgumentList ($inventory) #| Select Name, Platform, Type, Tier, Status, ServerName, WorkingDirectory
return $return_object
}
Answer:
$inventory = $args[0] ---> $inventory = $args

Try $inventory = $args instead of $inventory = $args[0].
Passing arrays to script blocks is not the easiest thing because $args flattens everything to a single array. If you pass -ArgumentList #(1,2,3), 4, then $Args will be a single array #(1,2,3,4) and $Args[0] will be 1.
If you ever need to pass complex arguments with -ArgumentList, pass them all as a single HashTable: -ArgumentList #{FirstArg = #(1,2,3); SecondArg = 4}.

Related

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

Using Splat in Invoke-Command AND passing arguments relevant to the machine

I'm using Invoke-Command, but this question can be relevant to any command using splat. I essentially want to pass two sets of variables that will be useful in the splat command, but I'm not sure how I can do this.
In the code below, the Invoke-Command successfully connects to both servers, but the output I get is "Server1 Info" from both servers, which makes sense since the code is reading it like I'm trying to pass two arguments to both servers and it is taking what is in the first argument and writing it to host. What I really want it to do though is only pass one argument each time and to move down the list of which argument is being passed as it connects to successive servers.
$ServerList = "Server1","Server2"
$ServerArgs = "Server1 Info","Server2 Info"
$SB = {
param($Arg1)
Write-Host $Arg1
}
$SplatInfo = #{
ComputerName = $ServerList
ArgumentList = $ServerArgs
}
Invoke-Command #SplatInfo -ScriptBlock $SB
You can only send one set of arguments per invocation of Invoke-Command.
If you want to pass different arguments per remote machine, you need to call Invoke-Command once per server:
$ServerList = "Server1","Server2"
$ServerArgs = "Server1 Info","Server2 Info"
$SB = {
param($Arg1)
Write-Host $Arg1
}
for($i = 0; $i -lt $ServerList.Count; $i++){
$SplatInfo = #{
ComputerName = $ServerList[$i]
ArgumentList = $ServerArgs[$i]
}
Invoke-Command #SplatInfo -ScriptBlock $SB
}
... in which case you might want to organize the input data slightly differently:
$inputList = #(
#{ ComputerName = "Server1"; ArgumentList = #("Server1 Info") }
#{ ComputerName = "Server2"; ArgumentList = #("Server2 Info") }
)
$baseSplat = #{
ScriptBlock = {
param($Arg1)
Write-Host $Arg1
}
}
foreach($entry in $inputList){
Invoke-Command #baseSplat #entry
}
Using the hashtable idea. This should run in parallel still.
import-csv file.csv |
% { $ServerArgs = #{} } { $ServerArgs[$_.server] = $_.args }
$ServerList = $ServerArgs | % keys
$SB = {
param($Arg1)
[pscustomobject]#{result = $Arg1[$env:computername]}
}
$SplatInfo = #{
ComputerName = $ServerList
ArgumentList = $ServerArgs
ScriptBlock = $SB
}
Invoke-Command #SplatInfo
result PSComputerName RunspaceId
------ -------------- ----------
Server1 Info Server1 gacbbb30-2492-41df-b181-9ebf6395b8b6
Server2 Info Server2 ebca4b52-c349-4ff7-972e-e67d07d9c0c3

Changing HideFastUserSwitching value in PowerShell script using Invoke-Command on a remote machine

I have written a below script which prompts user to enter a remote computer name, checks "HideFastUserSwitching" value in the registry of the computer and changes it if user wishes so.
The script does its job, I have tested to change the value from 1 to 0 on one machine. But if I run the script again and try to change it back to 1, it doesn't do that. The value stays 0.
Could you please help me figure out why does this happen? If you have remarks about other pieces of code in this script, you are welcome to give me some advise, it will be appreciated.
$PN = (Read-Host "Enter PN#").ToUpper().Trim()
$path = "HKLM:\Software\Microsoft\Windows\CurrentVersion\Policies\System"
$regKeyName = "HideFastUserSwitching"
$hideFastUserSwitchingValue = $null
Function getHideFastUserSwitchingValue {
$regKeyValue = $null
$parameters = #{
ComputerName = $PN
ScriptBlock = {
Param ($path, $regKeyName)
Get-ItemProperty -Path $path -Name $regKeyName | Select-Object -ExpandProperty $regKeyName
}
ArgumentList = $path, $regKeyName
}
Invoke-Command #parameters
}
Function setHideFastUserSwitchingValue($value) {
$parameters = #{
ComputerName = $PN
ScriptBlock = {
Param ($path, $regKeyName)
Set-ItemProperty -Path $path -Name $regKeyName -Value $value
}
ArgumentList = $path, $regKeyName
}
Invoke-Command #parameters
}
$hideFastUserSwitchingValue = getHideFastUserSwitchingValue
$yesNo = (Read-Host "HideFastUserSwitching value is $hideFastUserSwitchingValue. Do you want to modify it [y/n]?").ToUpper().Trim()
if($yesNo -eq "Y") {
$input = Read-Host "Enter the new value"
setHideFastUserSwitchingValue($input)
$hideFastUserSwitchingValue = getHideFastUserSwitchingValue
Write-Host "HideFastUserSwitching value is now $hideFastUserSwitchingValue"
} else {
$hideFastUserSwitchingValue = getHideFastUserSwitchingValue
Write-Host "HideFastUserSwitching value has not been modified and is equal to $hideFastUserSwitchingValue"
}

Start-Job not returning custom class object properly when PSCustomObject returns as intended

I'm running into a problem that isn't really a hindrance, but a possible bug from serialization in extracting job output (guessing?). The code below is a snippet from Start-Job and later captured output by foreach (Start-Job | Receive-Job)
class ComputerResult {
$computerName
$bldg
$room
$organization
$user
$lastUpdate
$printerlist = #()
$finalprinterlist = #()
}
# This one below doesn't work.
$return_result = New-Object -TypeName ComputerResult
# This done does work.
$return_result = New-Object -TypeName PSCustomObject -Property #{ComputerName = ""; BLDG = ""; room = ""; organization = ""; user = ""; lastupdate = ""; printerlist = #(); finalprinterlist = #() }
# Here I would start assigning values to $return_result
# Once assigned, return from the Job process to await Receive-Job
return $return_result
The main issue is the $return_result where it works as intended when it's a type PSCustomObject, unlike the ComputerResult class object defined above it. When the script runs, a Get-WmiObject -Class win32_printer -ComputerName $computername Is done to add some WMI objects into my $return_result.printerlist, but when returned, the ComputerResult.printerlist is returning an array of strings - with values of the __PATH property. What should be returned is WMI objects.
PSCustomObject returns just fine with keeping its methods, properties, etc. The ComputerResult.printerlist is kept with full WMI objects.
My assumption is the PSCustomObject is handled differently from the rest of the custom classes, in some way, and perhaps uses a different underlying library when serializing and piping back into the main process.
Why is this? Is this a bug or am I misunderstanding something?
I created a function with your code like this:
Function RetPrinter
{
class ComputerResult {
$computerName
$bldg
$room
$organization
$user
$lastUpdate
$printerlist = #()
$finalprinterlist = #()
}
$return_result = New-Object -TypeName ComputerResult
$return_result.PrinterList += gwmi win32_printer
return $return_result
}
I then retrieved my printer list like so:
$Obj = RetPrinter
By piping them to gm, $Obj was returned as TypeName:ComputerResult, and $ObjName.PrinterList was seen as TypeName: System.Management.ManagementObject#root\cimv2\Win32_Printer. All seemed to work as expected.

Creating a before and after from a Registry key value on a remote server

I'm a little lost as to why I'm unable to get a registry key value before, make a change then collect the value after the change to confirm the change was successful.
I'm using the following code which gets the correct values but doesn't reflect any changes I make to LogMaxHistory or LogLevel.
Am I doing something incorrect?
foreach ($server in $servers) {
$Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey("LocalMachine", $server)
$RegSubKey = $Reg.OpenSubKey("SOFTWARE\\Microsoft\\CCM\\Logging\\#Global", $true)
$LogLevelBefore = $RegSubKey.GetValue('LogLevel')
$LogMaxHistoryBefore = $RegSubKey.GetValue('LogMaxHistory')
$RegSubKey = $Reg.SetValue('LogLevel', '0', [Microsoft.Win32.RegistryValueKind]::DWORD)
$RegSubKey = $Reg.SetValue('LogMaxHistory', '6', [Microsoft.Win32.RegistryValueKind]::DWORD)
$RegCheck = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey("LocalMachine", $server)
$RegCheck = $RegCheck.OpenSubKey("SOFTWARE\\Microsoft\\CCM\\Logging\\#Global")
$LogLevelAfter = $RegCheck.GetValue('LogLevel')
$LogMaxHistoryAfter = $RegCheck.GetValue('LogMaxHistory')
[pscustomobject]#{
ComputerName = $server
LogLevelBefore = $LogLevelBefore
LogMaxHistoryBefore = $LogMaxHistoryBefore
LogLevelAfter = $LogLevelAfter
LogMaxHistoryAfter = $LogMaxHistoryAfter
}
$Reg.Close()
$RegCheck.Close()
GSV -ComputerName $server -Name CcmExec | Restart-Service
}
You can use Regquery
example
$before=reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\" /ve /s
$after=reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\" /ve /s
write-host "registry value $before and $after" -F green
Have you tried overloaded method OpenSubKey with RegistryKeyPermissionCheck type and RegistryKeyPermissionCheck.ReadWriteSubTree argument (see: MSDN)?
Another aproach is to use registry drives as follows (simplified, you must change keys and properties):
$server = '.'
Invoke-Command -ComputerName $server {
$before = Get-ItemProperty HKLM:\SOFTWARE\PDProgs -Name Property1
Set-ItemProperty HKLM:\SOFTWARE\PDProgs -Name Property1 -Value (Get-Random)
$after = Get-ItemProperty HKLM:\SOFTWARE\PDProgs -Name Property1
[psobject]#{Before = $before.Property1; After = $after.Property1}
} `
| % { "Before = $($_.Before), After = $($_.After)" }