Automating Windows Server Updates with PSwindowsUpdate module. Issue - windows

I am trying to automate windows server update instllation for multiple servers. I have installed the module on all servers and also added the hostnames in winrm trust hosts.
All server hostnames are stored in txt file and are looped trought for each loop with different commands from teh PSwindowswupdate module.
$Hostname = Get-Content -Path "C:\TEMP\powershell_patching_script\module\hostnamesallwsus.txt"
Import-Module PSWindowsUpdate
foreach ($i in $Hostname) {
write-host $i
Get-WUHistory -ComputerName $i -last 3
}
Issue is that randomly the loop is failing for some hostnames, with error :
BGxxxxxxx01 #this is the hostname
Get-WUHistory : BGxxxxxxx01: Unknown failure.
At C:\TEMP\powershell_patching_script\Module\History.ps1:10 char:1
+ Get-WUHistory -ComputerName $i -last 3
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : PermissionDenied: (:) [Get-WUHistory], Exception
+ FullyQualifiedErrorId : Unknown,PSWindowsUpdate.GetWUHistory
If I run the command with the hostname instead of variable it is failing again with the same error.
If I run the same but with $ in front of the hostname (even if such varaiable is not defined) the command works!ly appriciated
Get-WUHistory -ComputerName $BGxxxxxxx01 -last 3
Localy executed the commands are also working.
This issue seams to occure on random bases for multiple hostnames form my list.
I am unable find anything helpful regarding this error.
Any help will be highly appriciated!
Thanks in advance!

I found that Invoke-command works.
Just need to put the command in the script block of Invoke-command.

Related

BrokenCimSession when stopping a scheduled task

When running the following on one of our Windows machine
$name = "My task"
Stop-ScheduledTask -TaskName $name -ErrorAction SilentlyContinue
I get this error:
Stop-ScheduledTask : Cannot connect to CIM server. The system cannot find the file specified.
+ Stop-ScheduledTask -TaskName $name -ErrorAction SilentlyContinue
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ResourceUnavailable: (PS_ScheduledTask:String) [Stop-ScheduledTask], CimJobException
+ FullyQualifiedErrorId : CimJob_BrokenCimSession,Stop-ScheduledTask
It does not look like a permission issus since it would look more like
Stop-ScheduledTask : Access is denied.
+ [void](Stop-ScheduledTask -TaskName $name -ErrorAction SilentlyContinue)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : PermissionDenied: (PS_ScheduledTask:Root/Microsoft/...p_ScheduledTask) [Stop-Schedul
edTask], CimException
+ FullyQualifiedErrorId : HRESULT 0x80070005,Stop-ScheduledTask
A couple of facts to help:
I'm not running this remotely
Get-ScheduledTask will throw the same CimJob_BrokenCimSession error
Are you an administrator on the system? The error says permission denied (Misread OP)
When troubleshooting, it generally helps if you don't have -erroraction silentlycontinue if you actually want to see what's erroring. Obviously, you don't have it in a try/catch block, so you can see the error, but having the option to suppress errors doesn't make sense here.
More suggestions:
Are you running this command remotely? If you're running it against many servers, check your server list.
What happens if you get all the tasks, e.g. $tasks = get-scheduledtask, then reference the one you want with $tasks | where taskname -like "whatever" | stop-scheduledtask (or its index number in the array, e.g. $tasks[5]).
I didn't get any errors when I ran the preceding command against tasks that weren't running, but perhaps it does return an error in some cases if the task isn't in the running state.

Adding values from multiple computers with custom headers in a single csv

I need to accomplish the scenario below and for that I have to create a couple of powershell scripts to accomplish it.
The environment: Windows servers and Windows clients
Scenario
1- Create a script to be run in a specific time every day (with Task Scheduler) on windows clients. This script will push the current computer's hostname and IP address to a csv file with a specific headers (let's call these "Hostnames" and "IP Address"). These header shouldn't be changed as these scripts run from multiple computers at that time and all computers' data should be appended to each header, not overwrite them as this operation continues.
2- From a server, (after 15 mins) as fetching this "computer" list (csv), there should be a ping check for each of them using their IP addresses. If pings are successful on these remote computers, it should say "This computer is up" next to the each computer name and its IP address. If pings are unsuccessful, it should say "This computer is down" next to the each computer name and its IP address. Above these "status" information, there should be another header (let's say "IsAlive"). This header should be added to the csv as well.
So with this setup, I could be able to learn which computers are UP at a specific time and after I trigger some actions them, I could be able to learn if they're still up or down.
To be honest, I couldn't take a long way for it. I started to write a script about the first step but I couldn't combine the headers with values adding under them.
$header="Hostname"
$outputfile="\\10.10.10.40\reports\upcomputers.csv"
Add-Content $outputfile -Value $header
$hostname >> $outputfile
If I use this script (even if with one header), it's adding "NULL" after each alphabet of hostname and it doesn't append the other hostname under the "Hostname" header.
Additionally, I have no idea where to start adding the third header (IsAlive) and add each Test-NetConnection query's output as checking their IP addresses. I request you to show me a way to start with this section as well.
Finally, my output should be like that;
For the first step;
For the second step;
Thank you for your help and information
Stated on the main body of the request
Stage 2:
The easy way of doing this using PSCustomobject. Please find the sample code below:
$outputcsv = "C:\PowerShell\pingstatus.csv"
$hostlist = import-csv "C:\PowerShell\hostlist.csv"
$result = foreach($line in $hostlist){
$pingtest = Test-Connection -ComputerName $line.Hostname -Quiet -Count 1 -ErrorAction SilentlyContinue
if($pingtest) {
$OutputMessage = "This computer is up"
}
else {
$OutputMessage = "This computer is down"
}
[pscustomobject][ordered]#{
HostName = $line.Hostname
IPAddress = $line.IPaddress
IsAlive = $OutputMessage
}
}
$result | Export-csv -Path $outputcsv -NoTypeInformation
The Hostname and IPAddress input will be taken as input.
Note: Your input csv file should contain Hostname IPaddress as header.
Stage1:
why not?
$outputfile="\\10.10.10.40\reports\upcomputers.csv"
$serverDetails = [PSCustomObject]#{
Hostname = "$env:COMPUTERNAME"
IPAddress = (Get-WmiObject -Class Win32_NetworkAdapterConfiguration | where {$_.DHCPEnabled -ne $null -and $_.DefaultIPGateway -ne $null}).IPAddress | Select-Object -First 1
}
$serverDetails | Export-csv $outputfile -Append -NoTypeInformation
There are multiple ways to get IP address a computer, I used Get-WMIObject. You may use other simple ways like Test-Connection or Get-NetIPAddress.
To learn more: Please see
[PsCustomObject]: https://learn.microsoft.com/en-us/powershell/scripting/learn/deep-dives/everything-about-pscustomobject?view=powershell-7.3

New-WebBinding: Cannot retrieve the dynamic parameters for the cmdlet

We're using Windows 2012 Server R2.
We're trying to automate the creation of LetsEncrypt certificates. We're using LetsEncrypt-Win-Simple (https://github.com/Lone-Coder/letsencrypt-win-simple).
Once the cert is created (via LetsEncrypt.exe) we have a .bat script that gets called (using the --script and --scriptparameters flags). This runs powershell.exe and tries to create the necessary IIS binding. The line in the .bat file is:
powershell.exe -file c:\temp\SSLIISBinding.ps1 %1 %2 %3 %4
The %1-4 are args passed in by LetsEncrypt. In the powershell script, the command we're trying to run is:
$iis_host_name = $args[0]
$iis_site_name = $args[1]
$certificate_hash = $args[2]
$certificate_store = $args[3]
"IIS Host Name: " + $iis_host_name
"IIS Site Name: " + $iis_site_name
"Certificate Hash: " + $certificate_hash
"Certificate Store: " + $certificate_store
$guid = [guid]::NewGuid().ToString("B")
netsh http add sslcert hostnameport="${iis_host_name}:443" certhash=$certificate_hash certstorename=$certificate_store appid="$guid"
New-WebBinding -name $iis_site_name -Protocol https -HostHeader $iis_host_name -Port 443 -SslFlags 1
The args are passed into the .bat fine, as we output them and they are showing correctly.
If we run the .bat file on its own, it works perfectly. If it gets called by LetsEncrypt.exe it fails, reporting the following issue:
New-WebBinding : Cannot retrieve the dynamic parameters for the cmdlet.
Retrieving the COM class factory for component with CLSID
{688EEEE5-6A7E-422F-B2E1-6AF00DC944A6} failed due to the following error:
80040154 Class not registered (Exception from HRESULT: 0x80040154
(REGDB_E_CLASSNOTREG)).
At C:\temp\SSLIISBinding.ps1:13 char:1
+ New-WebBinding -name $iis_site_name -Protocol https -HostHeader
$iis_host_name ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~
+ CategoryInfo : InvalidArgument: (:) [New-WebBinding], Parameter
BindingException
+ FullyQualifiedErrorId : GetDynamicParametersException,Microsoft.IIs.Powe
rShell.Provider.NewWebBindingCommand
I've googled, some mentioning something about 32bit vs 64bit powershell, but I've tried using all the different powershell.exe available.
Anyone hit this issue, or know to resolve.
If we call .bat directly from command line it works fine, just as part of being called via LetsEncrypt.exe. A permission problem? Wrong powershell.exe?
That part of your question:
I've googled, some mentioning something about 32bit vs 64bit powershell
is already half of an answer. Some commands do not run properly if bitness of PowerShell process does not match bitness of operation system. So, you need to run powershell.exe, which located in this %windir%\System32\WindowsPowerShell\v1.0\ directory. But there is a little problem described in this documentation topic:
In most cases, whenever a 32-bit application attempts to access %windir%\System32, the access is redirected to %windir%\SysWOW64.
Thus, if 32-bit program on 64-bit OS invoke %windir%\System32\WindowsPowerShell\v1.0\powershell.exe, it will actually invoke 32-bit version of PowerShell from here %windir%\SysWOW64\WindowsPowerShell\v1.0\ instead of 64-bit one. To actually invoke 64-bit PowerShell from 32-bit application you need to use this trick:
32-bit applications can access the native system directory by substituting %windir%\Sysnative for %windir%\System32. WOW64 recognizes Sysnative as a special alias used to indicate that the file system should not redirect the access.
I've got the same error when running the following cmdlet:
PS> Remove-WebAppPool -Name 'Test'
Remove-WebAppPool : Cannot retrieve the dynamic parameters for the cmdlet. Retrieving the COM class factory for
component with CLSID {688EEEE5-6A7E-422F-B2E1-6AF00DC944A6} failed due to the following error: 80040154 Class not
registered (Exception from HRESULT: 0x80040154 (REGDB_E_CLASSNOTREG)).
At line:1 char:1
+ Remove-WebAppPool -Name 'Test'
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Remove-WebAppPool], ParameterBindingException
+ FullyQualifiedErrorId : GetDynamicParametersException,Microsoft.IIs.PowerShell.Provider.RemoveAppPoolCommand
The reason was because I ran it using Windows PowerShell (x86) on my Windows 10 x64 machine.
When I tried the same but using Windows PowerShell, which is 64 bit version, it worked just fine.
I think your $guid is the issue. The GUID needs to be the GUID of the program to bind the cert to. For your example port 443 is only bound to a random GUID, and not your program's GUID. IIS and other apps have a static GUID that you will want to use. If the GUID for a powershell script then Get-host is the powershell host executing code so that's the GUID you need. It changes for every powershell session and the netsh binding needs to as well.
$appid = "appid={"+(get-host).InstanceId.guid+"}"
$certhash = ls Cert:\LocalMachine\my | where {$.EnhancedKeyUsageList -Match 'Server' -and $.subject -match (hostname)}|sort-object $_.NotAfter|select -expand Thumbprint -last 1
$cmdline='netsh http add sslcert ipport=0.0.0.0:443 certhash=' + $certhash + ' "' + $appid + '"'
netsh http delete sslcert ipport=0.0.0.0:443
Invoke-Expression $cmdline
A google search for "Cannot retrieve the dynamic parameters for the cmdlet" brought me here but my issue was using powershell from the command line, and the answer was to escape the double quotes on the command...
I've got a problem with the same error. This happens when i'm trying to Add-WebBinding to my IIS site remotely, using Invoke-Command from different agent machines at time.
It's worked for me, maybe it helps someone too:
$Mutex = New-Object -TypeName System.Threading.Mutex($false, "Global\Mutex")
if ($Mutex.WaitOne(300000)) {
#For example
#$Command = {
#New-WebBinding -name $iis_site_name -Protocol https -HostHeader
#$iis_host_name -Port 443 -SslFlags 1
#}
#Invoke-Command -Command $Command
} else {
Write-Warning "Timed out acquiring mutex!"
}
$Mutex.Dispose()

PowerShell: Invoke-Parallel throwing error with "start-process"

I'm using the Invoke-Parallel CmdLet that can be found at the link here. When I have some dummy code in my script block, it works correctly, but when I add a start-process... command it fails with an error saying
Get-RunspaceData : This command cannot be run due to the error: The system
cannot find the file specified.
At c:\crm\Interfaces\Batches\Invoke-Parallel.ps1:592 char:13
My script block looks like so. Long story short, I'm feeding file names into a block, and I am telling a 3rd party application to use that file as an input for a process. When the start-process line is removed, it works, with it, it fails.
$childFiles| Invoke-Parallel -ImportVariables {
$importformat = '-f ' + '"CU Imp P19 FI"'
$importfile = '-d ' + $_ + " " + $user + " " + $pass
Write-Host $importformat + " " + $_
start-process .\mmim.exe -ArgumentList $user, $pass, '-l:1',
$importformat, $importfile -Wait -NoNewWindow
return "blah"
}
Does anyone have any idea of what might be going on? My PowerShell version is the following
Major:5
Minor:0
Build: 10586
Revision: 117
Any help is greatly appreciated.
Each PowerShell runspace have its own current location. If Invoke-Parallel does not change current location of spawned runspaces to match current location of main runspace, then relative path .\mmim.exe may be resolved to entire different executable or not resolved at all and produce given error.

How to add more than one machine to the trusted hosts list using winrm

To run powershell commands on a machine from a remote machine we have to add the remote machine to the trusted hosts list of the host machine.
I am adding machine A to machine B's trusted hosts using the following command :
winrm set winrm/config/client ‘#{TrustedHosts="machineA"}’
How to add more machines say machine C, machine D to trusted hosts list of machine B?
I prefer to work with the PSDrive WSMan:\.
Get TrustedHosts
Get-Item WSMan:\localhost\Client\TrustedHosts
Set TrustedHosts
provide a single, comma-separated, string of computer names
Set-Item WSMan:\localhost\Client\TrustedHosts -Value 'machineA,machineB'
or (dangerous) a wild-card
Set-Item WSMan:\localhost\Client\TrustedHosts -Value '*'
to append to the list, the -Concatenate parameter can be used
Set-Item WSMan:\localhost\Client\TrustedHosts -Value 'machineC' -Concatenate
winrm set winrm/config/client '#{TrustedHosts="machineA,machineB"}'
The suggested answer by Loïc MICHEL blindly writes a new value to the TrustedHosts entry.
I believe, a better way would be to first query TrustedHosts.
As Jeffery Hicks posted in 2010, first query the TrustedHosts entry:
PS C:\> $current=(get-item WSMan:\localhost\Client\TrustedHosts).value
PS C:\> $current+=",testdsk23,alpha123"
PS C:\> set-item WSMan:\localhost\Client\TrustedHosts –value $current
I created a module to make dealing with trusted hosts slightly easier, psTrustedHosts. You can find the repo here on GitHub. It provides four functions that make working with trusted hosts easy: Add-TrustedHost, Clear-TrustedHost, Get-TrustedHost, and Remove-TrustedHost. You can install the module from PowerShell Gallery with the following command:
Install-Module psTrustedHosts -Force
In your example, if you wanted to append hosts 'machineC' and 'machineD' you would simply use the following command:
Add-TrustedHost 'machineC','machineD'
To be clear, this adds hosts 'machineC' and 'machineD' to any hosts that already exist, it does not overwrite existing hosts.
The Add-TrustedHost command supports pipeline processing as well (so does the Remove-TrustedHost command) so you could also do the following:
'machineC','machineD' | Add-TrustedHost
Same as #Altered-Ego but with txt.file:
Get-Content "C:\ServerList.txt"
machineA,machineB,machineC,machineD
$ServerList = Get-Content "C:\ServerList.txt"
$currentTrustHost=(get-item WSMan:\localhost\Client\TrustedHosts).value
if ( ($currentTrustHost).Length -gt "0" ) {
$currentTrustHost+= ,$ServerList
set-item WSMan:\localhost\Client\TrustedHosts –value $currentTrustHost -Force -ErrorAction SilentlyContinue
}
else {
$currentTrustHost+= $ServerList
set-item WSMan:\localhost\Client\TrustedHosts –value $currentTrustHost -Force -ErrorAction SilentlyContinue
}
The "-ErrorAction SilentlyContinue" is required in old PS version to avoid fake error message:
PS C:\Windows\system32> get-item WSMan:\localhost\Client\TrustedHosts
WSManConfig: Microsoft.WSMan.Management\WSMan::localhost\Client
Type Name SourceOfValue Value
---- ---- ------------- -----
System.String TrustedHosts machineA,machineB,machineC,machineD
winrm set winrm/config/client '#{TrustedHosts="ServerA"}'
Generates this error:
Syntax Error: input must be of the form {KEY="VALUE"[;KEY="VALUE"]}
This worked for me (Server 2016):
winrm set winrm/config/client #{TrustedHosts="ServerA"}

Resources