WMI CommandLineTemplate Variables Cutting Off - windows

I've been working on a solution to monitor and respond to certain windows services stopping, and I could really use a few hundred extra sets of eyes on this. I'm setting up the WMI subscription in Powershell and the subscription seems to do it's job, but I'm not getting the expected output using the CommandLineTemplate. I'm trying to push the service name, current state, and previous state to a powershell script or executable (same PS script but compiled) but I only get part of the first variable before it cuts off. I've tried formatting the commandlinetemplate multiple different ways, escaping the variables with single/double/escaped quotes, and tried re-ordering the variables, but it always seems to be part of the first and nothing else gets passed. For testing I'm just trying to grab the variables and write them to a log before I move on to the more fun stuff.
Subscription Code:
$instanceFilter = ([wmiclass]"\\.\root\subscription:__EventFilter").CreateInstance()
$instanceFilter.QueryLanguage = "WQL"
$instanceFilter.Query = "select * from __instanceModificationEvent within 5 where targetInstance isa 'win32_Service' AND targetInstance.Name LIKE 'ServiceNamex.%'"
$instanceFilter.Name = "ServiceFilter"
$instanceFilter.EventNamespace = 'root\cimv2'
$result = $instanceFilter.Put()
$newFilter = $result.Path
#Creating a new event consumer
$instanceConsumer = ([wmiclass]"\\.\root\subscription:CommandLineEventConsumer").CreateInstance()
$instanceConsumer.Name = 'ServiceConsumer'
$instanceConsumer.CommandLineTemplate = "C:\Tools\ServiceMonitor.exe `"%TargetInstance.Name%`" `"%TargetInstance.State%`" `"%PreviousInstance.State%`""
$instanceConsumer.ExecutablePath = "C:\Tools\ServiceMonitor.exe"
$result = $instanceConsumer.Put()
$newConsumer = $result.Path
#Bind filter and consumer
$instanceBinding = ([wmiclass]"\\.\root\subscription:__FilterToConsumerBinding").CreateInstance()
$instanceBinding.Filter = $newFilter
$instanceBinding.Consumer = $newConsumer
$result = $instanceBinding.Put()
$newBinding = $result.Path
Target Code (PS1/EXE):
[CmdletBinding()]
param (
[parameter(Position=0)][string]$serviceName = "Error",
[parameter(Position=1)][string]$currentState = "Error",
[parameter(Position=2)][string]$previousState = "Error"
)
Add-Content -path "C:\temp\service.log" -value "$(Get-Date) - The state of $serviceName on $env:Computername has changed from $previousState to $currentState."
If ($currentState -eq "Stopped")
{
Add-Content -path "C:\temp\service.log" -value "$(Get-Date) - Attempting to restart $serviceName."
Start-Service -DisplayName $serviceName
}
Example Output for ServiceNamex.Funct.QA.12 stopping and starting:
10/30/2019 03:52:11 - The state of ServiceNamex.Funct.QA. has changed to Error.
10/30/2019 03:52:16 - The state of ServiceNamex.Funct.QA. has changed to Error.

Chris, It looks like the issue lies in the variables that you're getting from the first script. I'm not sure where you're getting those variables from. The ServiceMonitor.exe application isn't a PS script and I don't see where the subscription calls out to ServiceMonitor.ps1
Just copying your PS lines into a ServiceMonitor.ps1 file, I was able to run the following command and get the subsequent log information.
.\ServiceMonitor.ps1 TestService Running Stopped
11/01/2019 11:01:24 - The state of TestService on 7D25PX1 has changed from Stopped to Running.
11/01/2019 11:13:11 - The state of TestService on 7D25PX1 has changed from Stopped to Running.
11/01/2019 11:13:44 - The state of TestService on 7D25PX1 has changed from Stopped to Running.
Hopefully that helps to at least point you in the right direction. Feel free to respond with more information and I'll be happy to update the post.

Related

Can't get Folder.GetDetailsOf() to return file time in seconds

I'm trying to access file created time information from files on my iPhone, when it's connected to my Windows laptop. I've put together a small PowerShell test script that succesfully does so, with one small shortcoming: the function Folder.GetDetailsOf(..., 4), which returns the file's created time, does not include seconds… (An example output would be "18/03/2017 21:58".)
How would I get Folder.GetDetailsOf() to return seconds also?
I've tried applying a different culture (locale):
Setting a new culture using the required time format;
Wrapping everything in Using-Culture, as described on the MSDN blog;
Setting the system-wide short time format (in Windows Settings => Clock, Language, and Region) to "HH:mm:ss".
None of this seems to work… However, the function Get-Date applies the new culture just fine. The main difference between these two functions, is that Folder.GetDetailsOf runs via a COM object (Shell.Application), whereas Get-Date does not. If using a different culture is indeed the way to go, then how could I apply them to COM objects?
Example script
Function Using-Culture (
[System.Globalization.CultureInfo]$culture = (throw “USAGE: Using-Culture -Culture culture -Script {scriptblock}”),
[ScriptBlock]$script= (throw “USAGE: Using-Culture -Culture culture -Script {scriptblock}”)
) {
$OldCulture = [System.Threading.Thread]::CurrentThread.CurrentCulture
trap {
[System.Threading.Thread]::CurrentThread.CurrentCulture = $OldCulture
}
[System.Threading.Thread]::CurrentThread.CurrentCulture = $culture
Invoke-Command $script
[System.Threading.Thread]::CurrentThread.CurrentCulture = $OldCulture
}
$currentThread = [System.Threading.Thread]::CurrentThread
[System.Globalization.CultureInfo] $culture = "nl-NL"
$culture.DateTimeFormat.LongTimePattern = 'HH:mm:ss'
$culture.DateTimeFormat.ShortTimePattern = 'HH:mm:ss'
$culture.DateTimeFormat.FullDateTimePattern = 'dd MMMM, yyyy HH:mm:ss'
$currentThread.CurrentCulture = $culture
$currentThread.CurrentUICulture = $culture
Using-Culture $culture {
# This outputs the date in Dutch, using the format above;
# if the format above is changed, then Get-Date's output does too
Get-Date
$path = 'C:\Windows\System32\notepad.exe'
$shell = New-Object -COMObject Shell.Application
$folder = Split-Path $path
$file = Split-Path $path -Leaf
$shellfolder = $shell.Namespace($folder)
$shellfile = $shellfolder.ParseName($file)
# GetDetailsOf() on the other hand is oblivious to the newly set culture
$shellfolder.GetDetailsOf($shellfile, 4)
}
Why do I need this?
When copying videos off my iPhone, the files get a new Created time on my laptop, so I lose the original (actual) time. The Modified time does get copied, but it's not always correct. (Not quite sure why, this sometimes also happens when I don't do anything with the video after having shot it.)
In order to fix the Created time, I'm looking to extract the Created time from the videos while they're still on the phone, so I can write them to a file, and fetch them again later.
Any other suggestions on how to tackle this problem are of course also welcome :)

What is happening with these PSDriveInfo objects?

I have a few scripts that create multiple instances of PSDrive to remote instances. I want to make certain that each instance of PSDrive created is cleaned up.
I have a Powershell module like the following. This is a simplified version of what I actually run:
function Connect-PSDrive {
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
$Root,
[String]
$Name = [Guid]::NewGuid().ToString(),
[ValidateSet("Registry","Alias","Environment","FileSystem","Function","Variable","Certificate","WSMan")]
[String]
$PSProvider = "FileSystem",
[Switch]
$Persist = $false,
[System.Management.Automation.PSCredential]
$Credential
)
$parameters = #{
Root = $Root;
Name = $Name;
PSProvider = $PSProvider;
Persist = $Persist;
}
$drive = $script:drives | Where-Object {
($_.Name -eq $Name) -or ($_.Root -eq $Root)
}
if (!$drive) {
if ($Credential) {
$parameters.Add("Credential", $Credential)
}
$script:drives += #(New-PSDrive #parameters)
if (Get-PSDrive | Where-Object { $_.Name -eq $Name }) {
Write-Host "The drive '$Name' was created successfully."
}
}
}
function Disconnect-PSDrives {
[CmdletBinding()]
param ()
$script:drives | Remove-PSDrive -Force
}
Each time I invoke the function Connect-PSDrive, I can see that a new drive is successfully created and a reference is added to $script:drives. At the end of the calling script, I have a finally block that invokes Disconnect-PSDrives and this fails with the following exception.
Remove-PSDrive : Cannot find drive. A drive with the name 'mydrive' does not exist.
At C:\git\ops\release-scripts\PSModules\PSDriveWrapper\PSDriveWrapper.psm1:132 char:22
+ $script:drives | Remove-PSDrive -Force
+ ~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (mydrive:String) [Remove-PSDrive], DriveNotFoundException
+ FullyQualifiedErrorId : DriveNotFound,Microsoft.PowerShell.Commands.RemovePSDriveCommand
I want to know why references to the PSDrive objects I created are available in $script:drives, and yet Remove-PSDrive fails to locate the objects.
I also want to know how I can manage these PSDrive instances without needing to return each instance to the calling script such that Disconnect-PSDrives works.
A few extra notes:
I'm creating these drives with the Persist flag as false.
Running these multiple times errors with too many multiple connections being made to a machine. This is why I think that connections are not being cleaned up. If my assumption is wrong, please kindly explain why connections are cleaned up.
I am a little surprised that it cannot remove from the object reference; but I assume that your issue is with scope. PSDrives are local scope by default so when your function exits, they are no longer visible. Use the -Scope parameter for New-PSDrive and you will likely be successful. (As a side note: during Disconnect-PSDrives you will likely want to clear the list in case of multiple calls.)
That being said, you should never need to clean up the PSDrives like you are doing. Likely the reason you are experiencing too many connections is, once again, a scoping issue (that is, they still exist but you no longer see them). Try running it multiple times where you close PowerShell and start a new instance each time--you will no longer see too many connections. Why? Because PowerShell cleans up all non-persistent drives at the end of your session. You do not need to clean up the drives between sessions/instances; and within an session/instance (assuming you have proper scoping) you can re-use the drives so there is no need to create duplicates; ergo, you should never really need this functionality. That being said, I might assume you have some niche use case for this?

AWS AMI -Filter Latest Version

Maybe I am trying to use AWS EC2 incorrectly, please help me out. I would like to make a base ami via a user data script, this is no problem, it works. However, the next step is to make an image, however since the object is untagged its kind of a pain to filter for it, I can add criteria for region, vpc, security group and state, this would find the object and I could build the image.
However I do not want to overwrite the existing image, so ideally i need to tag this with a name and version, no problem. But then I need the child images to find that image, and i would like to find via name and version, but dynamically, i.e. latest. In docker it is pretty straight forward as long as the container is tagged, to use latest the version can be omitted and it will auto pull the latest. Is there a similar technique here? What do you guys use? Am I possibly using this wrong?
Here is what I did:
Note: although this is for is tagging an instance, it can easily be modified to work with images.
Note: I am not a powershell power user, so if you see a glaring inefficiency, please let me know.
I use Jenkins to build the machine, as such it has environment variables that I use for the tagging, however it calls a powershell script that has this signature, so you could manually call or call it via another script:
param(
...
[Parameter(Mandatory=$true)][string]$Tag_Name,
[Parameter(Mandatory=$true)][string]$Tag_Version
)
Inside of this script, I set the instance tags like so:
#Get metadata from ec2 service
$identityDocument = (Invoke-WebRequest http://169.254.169.254/latest/dynamic/instance-identity/document/).Content | ConvertFrom-Json
$tags = #(
#{Key = "Name"; Value = $Tag_Name},
#{Key = "Version"; Value = $Tag_Version}
)
New-EC2Tag -Resource $identityDocument.instanceId -Tag $tags
In another script, I can query by name, find all instances, parse the result into a hash table of [InstanceId, Version], sort by Version and get the top one.
$instanceName = "hello-world"
$instances = GetHashTableOfFilteredInstances $instanceName
$instanceId = GetNewestInstance($instances)
Write-Host 'Information for ' $instanceName
Write-Host '================='
Write-Host 'The newest instance is ' $instanceId
Write-Host '================='
function GetHashTableOfFilteredInstances($tagName){
$instances = Get-EC2Instance -Filter #( `
#{name='tag:Name'; values=$tagName};`
) | Select-Object -ExpandProperty instances
$actInstances= #{}
foreach($instance in $instances){
foreach($tag in $instance.Tag){
if ($tag.Key -ne "Version") {
Continue;
}
$actInstances.Add($tag.Value, $instance.InstanceId)
}
}
return $actInstances
}
function GetNewestInstance($instances){
return ($instances.GetEnumerator() | Sort-Object Key -descending)[0].Value
}

PowerShell - If/Else Statement Doesn't Work Properly

First off I apologize for the extremely long, wordy post. It’s an interesting issue and I wanted to be as detailed as possible. I’ve tried looking through any related PowerShell posts on the site but I couldn’t find anything that helped me with troubleshooting this problem.
I've been working on a PowerShell script with a team that can send Wake-On-Lan packets to a group of computers. It works by reading a .csv file that has the hostnames and MAC’s in two columns, then it creates the WOL packets for each computer and broadcasts them out on the network. After the WOL packets are sent, it waits a minute and then pings the computers to verify they are online, and if any don’t respond it will display a window with what machines didn’t respond to a ping. Up until the final If/Else statement works fine, so I won't be going into too much detail on that part of the script (but of course if you want/need further details please feel free to ask).
The problem I’m having is with the final If/Else statement. The way the script is supposed to work is that in the ForEach loop in the middle of the script, the value of variable $PingResult is true or false depending on whether or not the computer responds to a ping. If the ping fails, $PingResult is $false, and then it adds the hostname to the $PingResult2 variable.
In theory if all of the machines respond, the If statement fires and the message box displays that it was a success and then the script stops. If any machines failed to respond, the Else statement runs and it joins all of the items together from the $PingResult2 variable and displays the list in a window.
What actually happens is that even if all of the machines respond to a ping, the If statement is completely skipped and the Else statement runs instead. However, at that point the $PingResult2 variable is blank and hence it doesn’t display any computer names of machines that failed to respond. In my testing I’ve never seen a case where the script fails to wake a computer up (assuming it’s plugged in, etc.), but the Else statement still runs regardless. In situations where the Else statement runs, I’ve checked the value of the $PingResult2 variable and confirmed that it is blank, and typing $PingResult2 –eq “” returns $true.
To add another wrinkle to the problem, I want to return to the $PingResult2 variable. I had to create the variable as a generic list so that it would support the Add method to allow the variable to grow as needed. As a test, we modified the script to concatenate the results together by using the += operator instead of making $PingResult2 a list, and while that didn’t give a very readable visual result in the final display window if machines failed, it did actually work properly occasionally. If all of the computers responded successfully the If statement would run as expected and display the success message. Like I said, it would sometimes work and sometimes not, with no other changes making a difference in the results. One other thing that we tried was taking out all of the references to the Visual Basic assembly and other GUI elements (besides the Out-GridView window) and that didn’t work either.
Any idea of what could be causing this problem? Me and my team are completely tapped out of ideas at this point and we’d love to figure out what’s causing the issue. We’ve tried it on Windows 7, 8.1, and the latest preview release of Windows 10 with no success. Thanks in advance for any assistance.
P.S Extra brownie points if you can explain what the regular expression on line 29 is called and how it exactly works. I found out about it on a web posting that resolved the issue of adding a colon between every two characters, but the posting didn’t explain what it was called. (Original link http://powershell.org/wp/forums/topic/add-colon-between-every-2-characters/)
Original WOL Script we built the rest of the script around was by John Savill (link http://windowsitpro.com/networking/q-how-can-i-easily-send-magic-packet-wake-machine-my-subnet)
Script
Add-Type -AssemblyName Microsoft.VisualBasic,System.Windows.Forms
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.ShowDialog() | Out-Null
$FileVerify = Get-Content -Path $OpenFileDialog.FileName -TotalCount 1
$FileVerify = ($FileVerify -split ',')
If($FileVerify[0] -ne "Machine Name" -or $FileVerify[1] -ne "MAC")
{
$MsgBox = [System.Windows.Forms.MessageBox]::Show("The CSV File's headers must be Machine Name and MAC.",'Invalid CSV File headers!',0,48)
Break
}
$ComputerList = Import-Csv -Path $OpenFileDialog.FileName |
Out-GridView -PassThru -Title "Select Computers to Wake up"
ForEach($Computer in $ComputerList)
{
If($Computer.'MAC' -notmatch '([:]|[-])')
{
$Computer.'MAC' = $Computer.'MAC' -replace '(..(?!$))','$1:'
}
$MACAddr = $Computer.'MAC'.split('([:]|[-])') | %{ [byte]('0x' + $_) }
$UDPclient = new-Object System.Net.Sockets.UdpClient
$UDPclient.Connect(([System.Net.IPAddress]::Broadcast),4000)
$packet = [byte[]](,0xFF * 6)
$packet += $MACAddr * 16
[void] $UDPclient.Send($packet, $packet.Length)
write "Wake-On-Lan magic packet sent to $($Computer.'Machine Name'.ToUpper())"
}
Write-Host "Pausing for sixty seconds before verifying connectivity."
Start-Sleep -Seconds 60
$PingResult2 = New-Object System.Collections.Generic.List[System.String]
ForEach($Computer in $ComputerList)
{
Write-Host "Pinging $($Computer.'Machine Name')"
$PingResult = Test-Connection -ComputerName $Computer.'Machine Name' -Quiet
If ($PingResult -eq $false)
{
$PingResult2.Add($Computer.'Machine Name')
}
}
If($PingResult2 -eq "")
{
[System.Windows.Forms.MessageBox]::Show("All machines selected are online.",'Success',0,48)
Break
}
Else
{
$PingResult2 = ($PingResult2 -join ', ')
[System.Windows.Forms.MessageBox]::Show("The following machines did not respond to a ping: $PingResult2",'Unreachable Machines',0,48)
}
The comparison in your If statement is incorrect because you are comparing $PingResult2, a List<string>, to a string. Instead, try
If ($PingResult2.Count -eq 0)
{
# Show the message box
}
Else
{
# Show the other message box
}
or one of countless other variations on this theme.
The regular expression in question uses a backreference to replace exactly two characters with the same two characters plus a colon character. I am unsure what exactly you are attempting to "define," though.
You are checking if a list has a value of a null string, rather than checking the number of items in the list.
If you change the if statement to the following it should work fine:
If($PingResult2.count -eq 0)
I'm guessing the regex is trying to insert a colon between every two characters of a string to represent 0123456789ab as 01:23:45:67:89:ab.
The code means if there is no hyphen or colon in the MAC, put in a colon every the characters, then split the address using colon as delimiter then represent each as a byte:
If($Computer.'MAC' -notmatch '([:]|[-])')
{
$Computer.'MAC' = $Computer.'MAC' -replace '(..(?!$))','$1:'
}
$MACAddr = $Computer.'MAC'.split('([:]|[-])') | %{ [byte]('0x' + $_) }
The other answer have explained quite well why your code does not work. I'm not going there. Instead I'll give some suggestions that I think would improve your script, and explain why I think so. Let's start with functions. Some of the things you do are functions I keep on hand because, well, they work well and are used often enough that I like having them handy.
First, your dialog to get the CSV file path. It works, don't get me wrong, but it could probably be better... As it is you pop up an Open File dialog with no parameters. This function allows you to use a few different parameters as wanted, or none for a very generic Open File dialog, but I think it's a slight improvement here:
Function Get-FilePath{
[CmdletBinding()]
Param(
[String]$Filter = "|*.*",
[String]$InitialDirectory = "C:\")
[void][System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms")
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.initialDirectory = $InitialDirectory
$OpenFileDialog.filter = $Filter
[void]$OpenFileDialog.ShowDialog()
$OpenFileDialog.filename
}
Then just call it as such:
$CSVFile = Get-FilePath -Filter "Comma Separated Value (.CSV)|*.CSV" -InitialDirectory "$env:USERPROFILE\Desktop"
That opens the dialog filtering for only CSV files, and starts them looking at their desktop (I find that a lot of people save things to their desktop). That only gets the path, so you would run your validation like you were. Actually, not like you were. You really seem to have over complicated that whole bit. Bit I'll get to that in a moment, first, another function! You call message boxes fairly often, and type out a bunch of options, and call the type, and everything every single time. If you're going to do it more than once, make it easy on yourself, make a function. Here, check this out:
Function Show-MsgBox ($Text,$Title="",[Windows.Forms.MessageBoxButtons]$Button = "OK",[Windows.Forms.MessageBoxIcon]$Icon="Information"){
[Windows.Forms.MessageBox]::Show("$Text", "$Title", [Windows.Forms.MessageBoxButtons]::$Button, $Icon) | ?{(!($_ -eq "OK"))}
}
Then you can specify as much or as little as you want for it. Plus it uses Type'd parameters, so tab completion works, or in the ISE (if that's where you're writing your script, like I do) it will pop up valid options and you just pick from a list for the buttons or icon to show. Plus it doesn't return anything if it's a simple 'OK' response, to keep things clean, but will return Yes/No/Cancel or whatever other option you choose for buttons.
Ok, that's the functions, let's get to the meat of the script. Your file validation... Ok, you pull the first line of the file, so that should just be a string, I'm not sure why you're splitting it and verifying each header individually. Just match the string as a whole. I would suggest doing it case insensitive, since we don't really care about case here. Also, depending on how the CSV file was generated, there could be quotes around headers, which you may want to account for. Using -Match will perform a RegEx match that is a bit more forgiving.
If((Get-Content $CSVFile -TotalCount 1) -match '^"?machine name"?,"?mac"?$'){
Show-MsgBox "The CSV File's headers must be Machine Name and MAC." 'Invalid CSV File headers!' -Icon Warning
break
}
So now we have two functions, and 5 lines of code. Yes, the functions take up more space than what you previously had, but they're friendlier to work with, and IMO more functional. Your MAC address correction, and WOL sending part are all aces so far as I'm concerned. There's no reason to change that part. Now, for validating that computers came back up... here we could use some improvement. Instead of making a [List] just add a member to each object, then filter against that below. The script as a whole would be a little longer, but better off for it I think.
Add-Type -AssemblyName Microsoft.VisualBasic,System.Windows.Forms
Function Get-FilePath{
[CmdletBinding()]
Param(
[String]$Filter = "|*.*",
[String]$InitialDirectory = "C:\")
[void][System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms")
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.initialDirectory = $InitialDirectory
$OpenFileDialog.filter = $Filter
[void]$OpenFileDialog.ShowDialog()
$OpenFileDialog.filename
}
Function Show-MsgBox ($Text,$Title="",[Windows.Forms.MessageBoxButtons]$Button = "OK",[Windows.Forms.MessageBoxIcon]$Icon="Information"){
[Windows.Forms.MessageBox]::Show("$Text", "$Title", [Windows.Forms.MessageBoxButtons]::$Button, $Icon) | ?{(!($_ -eq "OK"))}
}
#Get File Path
$CSVFile = Get-FilePath -Filter "Comma Separated Value (.CSV)|*.CSV" -InitialDirectory "$env:USERPROFILE\Desktop"
#Validate Header
If((Get-Content $CSVFile -TotalCount 1) -match '^"?machine name"?,"?mac"?$'){
Show-MsgBox "The CSV File's headers must be Machine Name and MAC." 'Invalid CSV File headers!' -Icon Warning
break
}
$ComputerList = Import-Csv -Path $CSVFile |
Out-GridView -PassThru -Title "Select Computers to Wake up"
ForEach($Computer in $ComputerList)
{
If($Computer.'MAC' -notmatch '([:]|[-])')
{
$Computer.'MAC' = $Computer.'MAC' -replace '(..(?!$))','$1:'
}
$MACAddr = $Computer.'MAC'.split('([:]|[-])') | %{ [byte]('0x' + $_) }
$UDPclient = new-Object System.Net.Sockets.UdpClient
$UDPclient.Connect(([System.Net.IPAddress]::Broadcast),4000)
$packet = [byte[]](,0xFF * 6)
$packet += $MACAddr * 16
[void] $UDPclient.Send($packet, $packet.Length)
write "Wake-On-Lan magic packet sent to $($Computer.'Machine Name'.ToUpper())"
}
Write-Host "Pausing for sixty seconds before verifying connectivity."
Start-Sleep -Seconds 60
$ComputerList|ForEach
{
Write-Host "Pinging $($_.'Machine Name')"
Add-Member -InputObject $_ -NotePropertyName "PingResult" -NotePropertyValue (Test-Connection -ComputerName $Computer.'Machine Name' -Quiet)
}
If(($ComputerList|Where{!($_.PingResult)}).Count -gt 0)
{
Show-MsgBox "All machines selected are online." 'Success'
}
Else
{
Show-MsgBox "The following machines did not respond to a ping: $(($ComputerList|?{!($_.PingResult)}) -join ", ")" 'Unreachable Machines' -Icon Asterisk
}
Ok, I'm going to get off my soap box and go home, my shift's over and it's time for a cold one.

Is there an easy way to check if CredSSP is enabled on a systems?

I am aware of the Get-WSManCredSSP function; however, this cmdlet does not work well in a script. This returns a long string similar to the following:
The machine is configured to allow delegating fresh credentials to the following target(s): wsman/*,wsman/*,wsman/*,wsman/*
This computer is configured to receive credentials from a remote client computer.
I cannot easily include this in a script that I am writing, so I'm looking for an alternative way to check CredSSP.
Can't you consider using this as documented in the CmdLet help: Gets the WS-Management CredSSP setting on the client (<localhost|computername>\Client\Auth\CredSSP).
On a local machine it gives :
(Get-Item WSMan:\localhost\Client\Auth\CredSSP).value
You can use it like this :
(Get-Item WSMan:\localhost\Client\Auth\CredSSP).value -eq $false
You can first test if WinRm is available :
(Get-Service -Name winrm ).Status
I was also struggling with the limitations of the Get-WSManCredSSP output, and found this helper script by Victor Vogelpoel/Ravikanth Chaganti to be really helpful.
Some examples:
Check if current machine has been configured as CredSSP server and/or client:
(Get-WSManCredSSPConfiguration).IsServer
(Get-WSManCredSSPConfiguration).IsClient
Check if a specified client machine has been set up for delegation:
Get-WSManCredSSPConfiguration | % { $_.ClientDelegateComputer.Contains('clientcomputername') }
(not intended as a replacement for the work of Vogelpoel & Chaganti, but as a quick summary of a quick reading of CredSSP.cs, so you can get a quick grasp of what it's doing - that said, it was tested on several systems I had at hand and seems to work)
function Get-WSManCredSSPState
{
$res = [pscustomobject]#{DelegateTo = #(); ReceiveFromRemote = $false}
$wsmTypes = [ordered]#{}
(gcm Get-WSManCredSSP).ImplementingType.Assembly.ExportedTypes `
| %{$wsmTypes[$_.Name] = $_}
$wmc = new-object $wsmTypes.WSManClass.FullName
$wms = $wsmTypes.IWSManEx.GetMethod('CreateSession').Invoke($wmc, #($null,0,$null))
$cli = $wsmTypes.IWSManSession.GetMethod('Get').Invoke($wms, #("winrm/config/client/auth", 0))
$res.ReceiveFromRemote = [bool]([xml]$cli).Auth.CredSSP
$afcPath = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\CredentialsDelegation\AllowFreshCredentials'
if (test-path $afcPath)
{
$afc = gi $afcPath
$res.DelegateTo = $afc.GetValueNames() | sls '^\d+$' | %{$afc.GetValue($_)}
}
return $res
}

Resources