Powershell : build dynamic invoke-command for SSL and NOSSL connections - powershell

We have servers that are setup with type type of WinRM connection Secure and normal.
I need to establish a remote powershell connection with or without the -usessl switch.
I wanted to avoid having two identical script blocks with the only difference being having or not having the -usessl switch.
I tried using argument splatting but can't deal with the -usessl switch, also tried using invoke-expression but that broke the ability to retrieve data; Hash value from the remote job.
Any suggestions ?
if (test-wsman -ErrorAction SilentlyContinue -cn "$($Cmpname.name).sample.com" –UseSSL ) {
Write-host $Cmpname.name,WSMan Connected SSL
$strWSMAN = "SSL"
$strRemoteHash = invoke-command -cn "$($Cmpname.name).sample.com" -usessl -scriptblock {
Write-host "calculating Hash values"
$strLocalhash = Get-ChildItem -Path "c:\Windows\ccmcache" -Filter "Windows10.0-KB4041691-x64.cab" -Recurse -ErrorAction SilentlyContinue -Force | Get-FileHash -Algorithm sha1
New-Object pscustomobject -Property #{
RemotePath = $strLocalhash.path
RemoteHash = $strLocalhash.hash
}
}
} else {
$strWSMAN = "NoSSL"
$strRemoteHash = invoke-command -cn "$($Cmpname.name).sample.com" -scriptblock {
Write-host "calculating Hash values"
$strLocalhash = Get-ChildItem -Path "c:\Windows\ccmcache" -Filter "Windows10.0-KB4041691-x64.cab" -Recurse -ErrorAction SilentlyContinue -Force | Get-FileHash -Algorithm sha1
New-Object pscustomobject -Property #{
RemotePath = $strLocalhash.path
RemoteHash = $strLocalhash.hash
}
}
}

As mjolinor said, you need to provide a boolean value for a switch parameter when splatting:
$ParameterValues = #{
ComputerName = "$($Cmpname.name).sample.com"
UseSSL = [bool](Test-WSMan -ErrorAction SilentlyContinue -ComputerName "$($Cmpname.name).sample.com" –UseSSL)
ScriptBlock = {
Write-host "calculating Hash values"
$strLocalhash = Get-ChildItem -Path "c:\Windows\ccmcache" -Filter "Windows10.0-KB4041691-x64.cab" -Recurse -ErrorAction SilentlyContinue -Force | Get-FileHash -Algorithm sha1
New-Object pscustomobject -Property #{
RemotePath = $strLocalhash.path
RemoteHash = $strLocalhash.hash
}
}
}
Invoke-Command #ParameterValues

Related

Check if process is running on multiple remote computers and copy a file if process is NOT running, keep list of those which succeeded

I need to copy a file to multiple computers, but can only do so if a particular app (process) is not running.
I know I can use Invoke-Command to run a script (scriptblock) on a list of machines.
But how can I check if process is running on the machine and then only copy file if it is not running.
So that at the end of running against a load of computers I can easily see those which succeeded e.g. process was not running and file was copied
Thanks
UPDATE:
I am assuming something like this will do the first bits of what I am asking, but how to visually show or log success or failure so I know which computers have been done - doesn't need to be anything fancy, even if simply a variable that holds computername of those where process wasn't running and file was copied okay
Invoke-Command -ComputerName PC1, PC2, PC3 -ScriptBlock {
If ((Get-process -Name notepad -ea SilentlyContinue) -eq $Null){
Copy-Item -Path "\\server01\c$\test\file.txt" -Destination "C:\test\file.txt" -Force
}
}
$Procs = invoke-command -ComputerName PC1 { get-process | Select Name }
If($Procs -notmatch "Notepad"){ Copy-Item -Path "\\server01\c$\test\file.txt" -Destination "\\$PC1\c$\test\" -Force}
edited:
$computers = #("PC1","PC2","PC3")
Foreach($computer in $computers){
$Procs = invoke-command -ComputerName $computer { Get-Process Notepad -ErrorAction SilentlyContinue}
If(!$Procs){"$Computer - not running Notepad"; Copy-Item -Path "\\server01\c$\test\file.txt" -Destination "\\$computer\c$\test\" -Force}
elseif($Procs){"$Computer - is running Notepad"}
}
Edit2(for clean output):
$computers = #("PC1","PC2","PC3")
$RNote = #()
$NNote = #()
$off = #()
Foreach($computer in $computers){
$TestC = Test-Connection -ComputerName $computer -Count 1
If(!($TestC)){$off += $computer} Else{
$Procs = invoke-command -ComputerName $computer { Get-Process Notepad -ErrorAction SilentlyContinue}
If(!$Procs){$NNote +=$computer; Copy-Item -Path "\\server01\c$\test\file.txt" -Destination "\\$computer\c$\test\" -Force}
elseif($Procs){$RNote +=$computer}
}
}
$leng =[array]$RNote.count,$NNote.Count,$off.count
[int]$max = ($leng | measure -Maximum).Maximum
for($i=0; $i -lt $max;$i++){
[pscustomobject]#{
"Notepad On" = $(if ($RNote[$i]){$RNote[$i]})
"Notepad Off" = $(if ($NNote[$i]){$NNote[$i]})
"Offline " = $(if ($off[$i]){$off[$i]})
}
}
I think this is what you're looking for or at least close:
$Results = #()
$Results +=
Invoke-Command -ComputerName DellXPS137000, DellXPS8920 -ScriptBlock {
$GPArgs = #{Name = "Notepad++"
ErrorAction = "SilentlyContinue"}
If ( $Null -ne (get-process #GPArgs )) {
#Process your copy here
$Status = "Success"
}
Else {$Status = "Failed"}
$Machine =
(Get-CimInstance -ClassName 'Win32_OperatingSystem').CSName
Return ,"$Machine : $Status"
}
Value of $Results:
PS> $results
DELLXPS137000 : Success
DELLXPS8920 : Failed
HTH

Powershell retrieving cert by Thumbprint as string versus string variable

I'm trying to piece together some PowerShell code to loop through a list of servers, return some info regarding their IIS sites and bindings, and if they have an https binding, get the certificateHash and use that locate the cert by thumbprint and return its expiration date.
The problem I am having is, when i run my code below $binding.cerficateHash seems to return what I would expect, a string of the cert Hash, but when I use that certificateHash property to try and get the cert by its thumbprint, it doesnt work... but when I take the raw string value of the certificateHash value and hardcode it, it works...
I've inspected the certificateHash.GetType() and it appears to be just a string, so i dont understand what im doing wrong, and ive tried a handful of things, with no avail, granted this is my first crack at powershell so there's lots I don't know.
$sites = Invoke-Command -ComputerName $serverName { Import-Module WebAdministration; Get-ChildItem -path IIS:\Sites } -ErrorAction SilentlyContinue
foreach($site in $sites)
{
$serverName
$site.name
$site.physicalPath
foreach($binding in $site.bindings.Collection)
{
$binding.protocol
$binding.bindingInformation
$binding.certificateHash
$binding.certificateStoreName
if($binding.certificateHash)
{
# This outputs AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
$binding.certificateHash
# this retrieves a cert and returns its expiration date, Woohooo!
Start-Job Invoke-Command -ComputerName $serverName -ScriptBlock { (Get-ChildItem -path Cert:\LocalMachine\WebHosting | Where-Object {$_.Thumbprint -eq "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" })[0].GetExpirationDateString() }
# this does not find a cert, and ive tried many things, and no dice.
Start-Job Invoke-Command -ComputerName $serverName -ScriptBlock { (Get-ChildItem -path Cert:\LocalMachine\WebHosting | Where-Object {$_.Thumbprint -eq $binding.certificateHash })[0].GetExpirationDateString() }
# i've tried extracting the hash via "tostring" and using that, no dice
$hash = $binding.certificateHash.ToString()
Start-Job Invoke-Command -ComputerName $serverName -ScriptBlock { (Get-ChildItem -path Cert:\LocalMachine\WebHosting | Where-Object {$_.Thumbprint -eq $hash })[0].GetExpirationDateString() }
# i've tried adding some wildcards and using the -like operator, no dice.
$hash = "*" + $binding.certificateHash + "*"
Start-Job Invoke-Command -ComputerName $serverName -ScriptBlock { (Get-ChildItem -path Cert:\LocalMachine\WebHosting | Where-Object {$_.Thumbprint -lilke $hash })[0].GetExpirationDateString() }
}
}
}
Example output for a site.
Site1
D:\Apps\site1
http
*:80:Site1-test.ourdomain.com
https
*:443:Site1-test.ourdomain.com
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
WebHosting
The computer you invoke the script block on doesn't know about the $binding variable in your local session. (That's also why it works when passing a literal string.)
Try passing the value as argument:
Invoke-Command -Computer $serverName -Script {
param ($hash)
(gci Cert:\LocalMachine\WebHosting | ? Thumbprint -eq $hash)[0].GetExpirationDateString()
} -Arg $binding.certificateHash

Invoke-command path null?

I am working on this command to be able to view and edit a registry key remotely to a computer on a joined domain when I need to test something. In this case, I am looking at Excel's "vbawarninsg" key. This works just fine.
cls
$computername = Read-Host "Enter computer name..."
Invoke-Command -ComputerName $computername {Get-ItemProperty -Path 'REGISTRY::HKEY_USERS\xxxxxxx\Software\Policies\Microsoft\office\16.0\excel\security' } |
Select-Object PSComputerName, vbawarnings, PSParentPath | fl
$name = "vbawarnings"
The next part is to set a new value for the "vbawarnings" key using New-ItemProperty. When I assigned a variable for the -Path name it gives me an error "Cannot bind argument to parameter 'Path' because it is null."
This is the script that gives me an error
cls
$computername = Read-Host "Enter computer name..."
$registryPath = 'REGISTRY::HKEY_USERS\xxxxxxx\Software\Policies\Microsoft\office\16.0\excel\security'
Invoke-Command -ComputerName $computername {Get-ItemProperty -Path $registryPath } |
Select-Object PSComputerName, vbawarnings, PSParentPath | fl
$name = "vbawarnings"
$value = Read-Host "To modify...Enter a value"
New-ItemProperty -Path $registryPath -Name $name -Value $value `
-PropertyType DWORD -Force -Verbose | Out-Null
Any help is greatly appreciated!
In order to use a variable remotely (such as the case with Invoke-Command), you need to use the $using: variable scope:
Invoke-Command -ComputerName $cn {
Get-ItemProperty -Path $using:regPath
}
or pass it as a parameter:
Invoke-Command -ComputerName $cn {
param($path)
Get-ItemProperty -Path $path
} -ArgumentList '-path', $regPath
See this article
When you do Invoke-Command, that scriptblock gets sent to the remote server.
Invoke-Command -ComputerName $computername {Get-ItemProperty -Path $registryPath }
On the remote server, $registryPath is null even though you have it locally in your script.
So just hardcode the registry path instead:
Invoke-Command -ComputerName $computername {Get-ItemProperty -Path 'REGISTRY::HKEY_USERS\xxxxxxx\Software\Policies\Microsoft\office\16.0\excel\security' }

Output from invoke-command not returning

I have the following scriptblock:
$scriptblock = {
$regpath = "HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp"
$securitylayer = Get-ItemProperty -Path $regpath -Name SecurityLayer -ErrorAction SilentlyContinue
If (!($securitylayer) -or ($securitylayer.securitylayer -ne '0')) {
Write-Host -ForegroundColor Yellow "Regkey not present or value not 0. Creating/setting to 0"
#Commented out for testing purposes
#Set-ItemProperty -Path $regpath -Name SecurityLayer -Value 0
}
Else {Write-Host -ForegroundColor green "Regkey present and set to 0. Skipping."}
}
that I pass to a PSSession on a remote machine running Server 2003 SP2:
$computername = 'computer'
$pssession = New-PSSession -ComputerName $computername -Name $computername
Invoke-Command -Session $pssession -ScriptBlock {$scriptblock}
But i don't see any output.
I've also tried
write-output
but still don't see any output.
I saw another post that suggested doing the following, however nothing was returned:
$scriptblock = {
$regpath = "HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp"
$securitylayer = Get-ItemProperty -Path $regpath -Name SecurityLayer -ErrorAction SilentlyContinue
If (!($securitylayer) -or ($securitylayer.securitylayer -ne '0')) {
new-object pscustomobject –property #{Result = "Regkey not present or value not 0. Creating/setting to 0"}
#Commented out for testing purposes
#Set-ItemProperty -Path $regpath -Name SecurityLayer -Value 0
}
Else {new-object pscustomobject –property #{Result = "Regkey present and set to 0. Skipping."}}
}
$computername = 'computer'
$pssession = New-PSSession -ComputerName $computername -Name $computername
$results = Invoke-Command -Session $pssession -ScriptBlock {$scriptblock}
$results.result
Code runs as expected when run on machine.
You are wrapping your scriptblock in a scriptblock, so it's not actually executing the script block. Just remove the {} from around the {$scriptblock}:
Invoke-Command -Session $pssession -ScriptBlock $scriptblock

Powershell ForEach loop with embedded IF statements

Starting to write powershell scripts (very new) because SCCM tends to respond better to them (both client and server)
So with the above stated here is my first script:
#Changes the 'ProvisioningMode' Key in the registry to False
$ProvisiongMode = New-ItemProperty -Path Registry::HKLM\SOFTWARE\Microsoft\CCM\CcmExec -Name ProvisioningMode -Value False -Force
#Clears or 'nulls' the SystemTaskExcludes key in the registry
$SystemTaskExludes = New-ItemProperty -Path Registry::HKLM\SOFTRWARE\Microsoft\CCM\CcmExec -Name SystemTaskExcludes - Value "" - Force
#----------------------------------------------------------------------------------------------
$Success = "C:\Path\to.log"
$Failure = "C:\Path\to.log"
$Computers = Import-Csv "C:\Path\to.csv"
$SearchStr = Get-ItemProperty -Path Registry::HKLM\SOFTWARE\Microsoft\CCM\CcmExec | select-object ProvisioningMode
$Online = Test-Conntection -Computername $ComputerName -Count 1 -Quiet
ForEach ($ComputerName in $Computers)
if ($Online -eq 'False')
{
Write-Output $ComputerName`t'Connection Failed' >> $Failure
}
Else
{
if ($SearchStr -eq True)
{
$ProvisioningMode
$SystemTaskExcludes
}
}
#Second Check
if ($SearchStr -eq 'False')
{
Write-Output $ComputerName`t'Registry has been changed' >> $Success
}
The issue in question is the $Online variable. I would like to see if a computer is responsive to ping, if true then proceed to run $ProvisioningMode and $SystemTaskExclude.
Then the other issue is querying that key to see if it changed. The issue with that one is $SearchStr = Get-ItemProperty -Path Registry::HKLM\SOFTWARE\Microsoft\CCM\CcmExec | select-object ProvisioningMode returns
ProvisionMode
-----------------
False
And I cant grab just the false data.
Like I stated; very new at powershell and writing something that I will use helps me learn.
Edit: What I Have tried is
ForEach ($Name in $Computers)
{
Test-Connection -BufferSize 2 -Computername $Name.ComputerName -Count 1 -Quiet | Write-Output $Online
}
if ($Online -eq 'True') {Write-Output $Name`t'Computer is online' >> C:\Online.txt}
And many variations of the same thing.
Test-Connection -BufferSize 2 -Computername $Name.ComputerName -Count 1 -Quiet
Returns Data, which is what I want, but I need to input that into an If statement and still retain the $Name for the $StringStr and log files.
Those of you wondering, this takes the client out of provisioning mode when running an OSD. It fixes the 'No Self-Signed Certificate' issue.
Even though the string representations of boolean values in PowerShell are True and False, the correct way to compare againt such a value is with the $true and $false variables.
Furthermore, assign the result of Test-Connection to $Online with =:
$Online = Test-Connection -BufferSize 2 -Computername $Name.ComputerName -Count 1 -Quiet
if($Online -eq $true){
# Machine responds to ping, do stuff!
}
But the comparison is actually unnecessary. If $Online already equals $frue or $false, you can use it on its own inside the if statement:
if($Online){
# Machine responds to ping, do stuff!
}
I assume that $ProvisionMode, $SystemTaskExcludes and $SearchStr are all statements that you want to execute on the remote machine, not on the SCCM server itself.
To do so, you will need to connect to the machine and instruct it to execute the *-ItemProperty statements.
# Enclosing statements in {} creates a ScriptBlock - a piece of code that can be invoked later!
$ProvisionMode = {
#Changes the 'ProvisioningMode' Key in the registry to False
New-ItemProperty -Path Registry::HKLM\SOFTWARE\Microsoft\CCM\CcmExec -Name ProvisioningMode -Value False -Force
}
$SystemTaskExludes = {
#Clears or 'nulls' the SystemTaskExcludes key in the registry
New-ItemProperty -Path Registry::HKLM\SOFTRWARE\Microsoft\CCM\CcmExec -Name SystemTaskExcludes - Value "" - Force
}
$SearchStr = {
Get-ItemProperty -Path Registry::HKLM\SOFTWARE\Microsoft\CCM\CcmExec | Select-Object -ExpandProperty ProvisioningMode
}
#----------------------------------------------------------------------------------------------
$LogFilePath = "C:\Path\to.log"
$Computers = Import-Csv "C:\Path\to.csv"
foreach($Computer in $Computers){
$Online = Test-Connection -Computername $Computer.Name -Count 1 -Quiet
if(-not $Online)
{
"$ComputerName`t'Connection Failed'" | Out-File -FilePath $LogFilePath -Append
}
else
{
$SearchResult = Invoke-Command -ComputerName $Computer.Name -ScriptBlock $SearchStr
if ($SearchResult)
{
# The call operator (&) invokes the scriptblock
Invoke-Command -ComputerName $Computer.Name -ScriptBlock $ProvisionMode
Invoke-Command -ComputerName $Computer.Name -ScriptBlock $SystemTaskExludes
}
else # SearchStr must be $false, or non-existing
{
"$ComputerName`t'Registry has been changed'" | Out-File -FilePath $LogFilePath -Append
}
}
}
For simplicity, I've used Invoke-Command with the -ComputerName parameter, but in a real world situation, I would set up a PSSession with New-PSSession, and reuse that for the connection with Invoke-Command -Session