Apply conditional to output of a commandlet - powershell

I want to output the result of the commandlet (Invoke-Command) on success and add a custom message if the result is null. The code as shown below produces the desired results except in the event of a null response, it simply outputs nothing on that line.
I can not pipe directly to an if statement, nor can I output on 2 opposing conditions (True & False). Is it possible to get a custom response on $null while not suppressing the normal output on success?
Invoke-Command -ComputerName PC1, PC2, PC3 -Scriptblock {get-eventlog system | where-object {$_.eventid -eq 129} | select MachineName, EventID, TimeGenerated, Message -last 1}
If you run the example code block assuming that PC1 and PC3 have the event ID but PC2 does not, the output will simply skip PC2.
I want to output something like "Event Not found" in that case.
Placing the entire thing in a loop and then running the results through another conditional loops destroys performance so that is not an ideal solution.

I would create a new object for returning from Invoke-Command. So you are sure you will receive from every host something even the event log is not present. And might you can change get-eventlog to Get-WinEvent. Get-WinEvent was for my tasks the most time faster than get-eventlog.
[System.Management.Automation.ScriptBlock]$Scriptblock = {
[System.Collections.Hashtable]$Hashtable = #{
WinEvent = Get-WinEvent -FilterHashtable #{ LogName = 'System'; Id = 129 } -MaxEvents 1 -ErrorAction SilentlyContinue #-ErrorAction SilentlyContinue --> otherwise there is an error if no event is available
}
return (New-Object -TypeName PSCustomObject -Property $Hashtable)
}
Invoke-Command -ComputerName 'PC1', 'PC2', 'PC3' -Scriptblock $Scriptblock

Related

Extracting part of a host name with select-object

I have a simple powershell function where I provide the log type and event and it scans all of our SQL servers. it works except the host name is returned as hostname.domain.local. I want it to return just the host name. I've tried machinename.split('.') and substring and it won't work. I've tried putting the select-object into a separate variable and was going to join it with the rest of the columns, but it takes too long to run.
Here is my sample scrap code i'm testing with before I change my function along with the commented out parts that didn't work. Looked around and found lots of resources about the commands, but they don't work when I try to use them in my script.
The error I keep getting is A positional parameter cannot be found that accepts argument '. '.
$servers = Get-Content -literalpath "C:\temp\sql_servers3.txt"
#$server
#$result =
ForEach($box in $servers) {Get-Eventlog -ComputerName $box -LogName
application -After 1-4-2018 -Entrytype Error | Where {$_.source -notin
'Perfnet','Perflib', 'ntfs', 'vss'}| select-object -property MachineName}
#$result_Host_name = select-object -inputobject $result -property
'MachineName'
#'TimeGenerated', 'MachineName'.Split('.')[1], 'EventID','message'}
#| Where {$_.source -notin 'Perfnet','Perflib', 'ntfs', 'vss'} 0
#return $result_Host_name
What you are looking for is a "Calculated Property" when using Select-Object.
| Select-Object #{n='HostName';e={($_.MachineName -split '\.')[0]}}

Taking input from one PSSession and sending it to another

Like many others, my background is in Linux with no powershell experience. So this object oriented programming is messing me up.
I need to search through VMware Horizon for VMs with users assigned to them, then check if they are disabled in AD. If they are disabled in AD I want to recycle the VM.
At the moment I am pulling the SIDs for the users from VMware Horizon, but when I try to use these in an invoke-command against AD I receive the following error
"Object reference not set to an instance of an object"
The Script so far
function getlist() {
$temp=Invoke-Command -ComputerName $vdiserver -ScriptBlock { add-pssnapin vmware.view.broker; get-desktopvm | select user_sid }
$list=$temp | Select-Object user_sid
#$list
}
$vdi1="server1"
$vdi2="server2"
$test=Test-Connection -ComputerName $vdi1 -Quiet
$test2=Test-Connection -ComputerName $vdi2 -Quiet
if ($test -eq "True"){
$vdiserver=$vdi1
getlist
}
elseif ($test2 -eq "True"){
$vdiserver=$vdi2
getlist
}
else {echo "No servers to connect to"}
ForEach ($user in $list) #{
#echo $user
#sleep 1
#}
{Invoke-Command -ComputerName domaincontroller -ScriptBlock {param($p1) get-aduser -identity $p1 } -argumentlist $user}
So this object oriented programming is messing me up.
So you're trying to revert to shell script, and writing twice as much code to do achieve half as much work.
The most important bit you're missing is to imagine an object as a collection of things - like, imagine you're working with /etc/passwd and each line has a user ID and a group ID and a home directory and a login shell.. and you're passing the entire line around at once, that's your analogous object.
An object has many properties, just like that (but overall more capable).
When you Select user_sid you're choosing that field to stay in the 'line', but the line is still something like :::user_sid:::: with the other fields now empty. (Approximately). But they're still there and in the way. To work with it directly, you have to get it out of the 'line' entirely - throw the container away and just have the user_sid outside of it.
get-desktopvm | select user_sid
->
get-desktopvm | select -expandproperty user_sid
which makes "sid1", "sid2", "sid3", but no containers for each sid.
This
function getlist() {
$temp=Invoke-Command -ComputerName $vdiserver -ScriptBlock { add-pssnapin vmware.view.broker; get-desktopvm | select user_sid }
$list=$temp | Select-Object user_sid
}
is essentially saying
function getlist() {
#do any amount of work here, and throw it all away.
}
Because the function returns nothing, and it doesn't change any data on disk or anything, so when the function finishes, the variables are cleared out of memory, and you can't use them afterwards.
This:
if ($test -eq "True"){
is a bit of a nonsense. It might work, but it's not working how you expect because it's happenstance that "a string with content" compared to a boolean True is True, regardless of the string containing the English word "True" or not. But it's also redundant - $test is itself true or false, you don't need to compare True with anything. if ($test). Or even if (Test-Connection -ComputerName $vdi -Quiet)
But stillll, so much work. Just connect to them all, and let it fail for the ones it can't contact. Maybe add -ErrorAction SilentlyContinue if you don't want to see the error.
$VMs = Invoke-Command -ComputerName Server1,Server2 -ScriptBlock {
Add-PsSnapin vmware.view.broker
Get-DesktopVm
}
Now you have all the VMs, get the user enabled/disabled state
foreach ($VM in $VMs) {
$Sid = $VM.user_sid
$AdEnabled = Invoke-Command -ComputerName domaincontroller -ScriptBlock {
(Get-AdUser -Identity $using:Sid).Enabled
}
$VM| Add-Member -NotePropertyName 'AdEnabled' -NotePropertyValue $AdEnabled
}
Now you should ideally have $VM as an array of objects, each one having all the VM Desktop properties - and also the True/False state of the AD Enabled property for that user account.
$VM | Out-Gridview
or
$VM | Export-Csv Report.csv
or
$VM | Where-Object { -not $_.AdEnabled }

Multiple column output using a hashtable

I am trying to create a Hash Table that contains 3 columns.
SERVER_NAME PROCESS_NAME SERVER_STATUS PROCESS_AVAILABLE
SERVER1 app1.exe RUNNING YES
SERVER1 app2.exe RUNNING NO
SERVER2 app1.exe OFFLINE NO
SERVER2 app2.exe OFFLINE NO
SERVER3 app1.exe RUNNING YES
SERVER3 app2.exe RUNNING YES
So far, I've tried this
$SERVERLIST = Get-Content "$PSScriptRoot\servers\serverManager.bin"
$PROCESSMONITOR = Get-Content "$PSScriptRoot\process\application.bin"
$testList = #{Name=$SERVERLIST;Process=$PROCESSMONITOR}
The list of servers are in the "serverManager.bin" file. This is a CSV file that contains a list of the servers.
The list of processes that I am interested in monitoring are in the "application.bin" file. This is a CSV file that contains a list of the applications (as seen by PowerShell). [see code below]
Get-Process -ComputerName $server -name $process -ErrorAction SilentlyContinue
I want to build a report which tells an admin which server is running and which process is running from the list that we are interested in monitoring.
I can check if the process is running
I can check if a server is online
My question is what do I need to do to get output like what's posted above
While hashtables play a part in this answer you are not looking for hashtables at all really. Looking at about_hash_tables
A hash table, also known as a dictionary or associative array, is a
compact data structure that stores one or more key/value pairs.
While you can nest whatever you want into the value you really are not looking for a hashtable. What I think you want is a custom PowerShell object that contains the results of each of your queries.
Get-Process does take arrays for both -Computer and -Name but they would omit results where either the computer does not exist or the process does not. Since you want that information you need to run a single cmdlet for each computer/process pair.
I use a hashtable only to create each individual "row" which is converted to a PowerShell object and collected as an array. I don't want to confuse but I know this working with at least 2.0 which is why I do it this way.
$SERVERLIST | ForEach-Object{
$computer = $_
$PROCESSMONITOR | ForEach-Object{
$process = $_
$props = #{
Server_Name = $computer
Process_Name = $process
}
# Check if the computer is alive. Better this was if $processes is large
If(Test-Connection $computer -Quiet -Count 1){
$props.Server_Status = "Running"
$result = Get-Process -Name $process -ComputerName $computer -ErrorAction SilentlyContinue
If($result){
$props.Process_Available = "Yes"
} else {
$props.Process_Available = "No"
}
} else {
$props.Server_Status = "Offline"
$props.Process_Available = "No"
}
New-Object -TypeName psobject -Property $props
}
} | Select Server_Name,Process_Name,Server_Status,Process_Available
So now that we have a proper object you can now use other cmdlets like Where-Object, Sort-Object and etc.

Display test-connection successes and failures in Out-Gridview

I am trying to get a list of servers and the last time they rebooted to show in a table. However, if it doesn't respond to a ping, I just need it to show in the list. I can't seem to figure out how to get it to add to the table after else.
Import-CSV $Downtime | % {
if(Test-Connection $_.server -Quiet -count 1){
Get-WmiObject Win32_OperatingSystem -ComputerName $_.server |
select #{LABEL="Name"; EXPRESSION = {$_.PSComputerName}}, #{LABEL="Last Bootup"; EXPRESSION = {$_.convertToDateTime($_.LastBootupTime)}}
}
else{#{LABEL="Name"; EXPRESSION = {$_.server}}
}
} | Out-GridView
I can always save the else results in a text file but this would be more convenient.
You need to make the same object, with the same properties!, in both cases so that PowerShell will understand the association between the two. The follwing example builds a custom hashtable using the if/else and outputs the object for each loop pass.
Import-CSV $Downtime | ForEach-Object {
$props = #{}
$server = $_.server
if(Test-Connection $server -Quiet -count 1){
$wmi= Get-WmiObject Win32_OperatingSystem -ComputerName $server
$props.Name = $wmi.PSComputerName
$props."Last Bootup" = $wmi.convertToDateTime($wmi.LastBootupTime)
}else{
$props.Name = $server
$props."Last Bootup" = "Could not contact"
}
New-Object -TypeName psobject -Property $props
} | Out-GridView
I used $server as the $_ changes context a couple of time so we wanted to be able to refer to the current row in the CSV we are processing.
I don't know what your PowerShell version is so I will assume 2.0 and create objects that support that.
In both cases an object is created with a Name and Last Bootup property which is populated based on the success of the ping.
As an aside I had a similar question a while ago about created similar object based output.

how to catch a System.Diagnostics.Eventing.Reader error in powershell

The code looks like this:
foreach ($machine in $lbx_workstations.SelectedItems)
{
$temp = (get-winevent -computername $machine -FilterXML $commandString -ErrorAction SilentlyContinue -ErrorVariable eventerr|
Select MachineName, TimeCreated, LevelDisplayName, ID, ProviderName, Message)
blah blah blah...
I made a custom error variable, $eventerr, which works just fine when the get-winevent cmdlet can't find any events that match the criteria in the XML commandstring. However, the problem is this: If the XML commandstring is invalid, the error is created in the $error variable instead of the $eventerr variable. I'd like to get that error stored in my custom error variable, but I don't know where it is coming from or what is generating it. Or why it isn't already in my custom variable, actually. When I look at these two different types of errors, this is the output I get:
PS C:\Temp> $error[0].fullyqualifiederrorid
System.Diagnostics.Eventing.Reader.EventLogException,Microsoft.PowerShell.Commands.GetWinEventCommand
PS C:\Temp> $error[1].fullyqualifiederrorid
NoMatchingEventsFound,Microsoft.PowerShell.Commands.GetWinEventCommand
I can catch the "NoMatchingEventsFound" error in the custom variable, but not the System.Diagnostics.Eventing... error.
Is there any way to get the "System.Diagnostics.Eventing... error into my custom error variable?
It's probably happening when PowerShell is trying to convert $commandString into an XmlDocument (the type of the -FilterXml parameter). I don't think you'll be able to capture the error in your error variable since it is happening before the call into Get-WinEvent. Your best bet is to validate $commandString before trying to pass it to Get-WinEvent.
try
{
$xmlDoc = New-Object 'Xml.XmlDocument'
$xmlDoc.LoadXml( $commandString )
}
catch
{
# Or do some other kind of error handling. You're the driver!
Write-Error ('Oh no! Bad XML! What are you thinking?!')
}
$lbx_workstationg.SelectedItems |
ForEach-Object { Get-WinEvent -ComputerName $_ -FilterXml $commandString -ErrorAction SilentlyContinue -ErrorAction eventerr } |
Select-Object MachineName, TimeCreated, LevelDisplayName, ID, ProviderName, Message
That being said, I have noticed that sometimes using the -ErrorVariable doesn't result in all the errors from getting put in the error variable specified. I never found out why. I usually end up falling back to using $Error. You can get the errors specific to your command by proper indexing:
$errorCount = $Error.Count
$lbx_workstationg.SelectedItems |
ForEach-Object { Get-WinEvent -ComputerName $_ -FilterXml $commandString -ErrorAction SilentlyContinue } |
Select-Object MachineName, TimeCreated, LevelDisplayName, ID, ProviderName, Message
$winEventError = $Error[0..($Error.Count - $errorCount)]
The two different errors make different types of error messages. These types have different properties.
$eventerr.message holds the text I need if the error is
TypeName: System.Management.Automation.CmdletInvocationException
The specified query is invalid
$eventerr.exception holds the error text if the error is this type:
TypeName: System.Management.Automation.ErrorRecord
No events were found that match the specified selection criteria.
I'm not clear on how a single variable (eventerr) can hold different object types. But this seems to be the case. If anyone has a good explanation for how this can be, I'd sure like to hear it. Meanwhile, I'm going to mark this as the answer.