When you need to install a hotfix outside of your normal patching cycle and outside of the usual patching tool (be that WSUS, SCCM...whatever) a good way is to do it semi-manually running wusa.exe.
However, if you have 20+ hosts, it might be a bit inconvenient to RDP to each and execute wusa or double-click on the msu file.
So why not use PS remoting or winrs? Let's give it a go (I copied the msu file to each host into their c:\temp folder prior to running winrs):
gc c:\hostlist.txt | %{winrs -r:$_ wusa.exe c:\temp\windows6.1-kbXXXXXXX.msu /passive /quiet /forcerestart}
But then you will get this:
Windows update could not be installed because of error 2147942405 "Access is denied.
Whaaaat? Why? WHY? A bit of googling will get you here: https://support.microsoft.com/en-us/help/2773898/windows-update-standalone-installer-wusa-returns-0x5-error-access-denied-when-deploying-.msu-files-through-winrm-and-windows-remote-shell
Ok, I will need to extract the msu and then run dsim on each host. It's no big deal, you can run the 2 commands in a remote PS session, or run 2 lines of winrs...etc.. But here is an alternative solution, psexec:
"host1,host2,host3".split(",") | %{start-proces psexec.exe -arg "-s \\$_ wusa.exe c:\temp\windows6.1-kbXXXXXXX.msu /passive /quiet /forcerestart"}
However, you can't just run psexec as it gets hung on wusa.exe, you need to kick it off with 'start-process' and then pass the rest of the arguments to it which will be for psexec which then executes wusa.exe under SYSTEM context.
Hope this helps.
t
Powershell one-liners and short scripts for real-life problems on large and complex Windows networks.
29 April, 2017
26 March, 2017
Split Array into smaller arrays - Scripting
Have you had a case when you had a long list of things and you wanted to split it up? Like a long shopping list which you really didn't want to tackle alone. Or you have a long list of IP addresses or host names, or object in Active Directory...whatever. And you want to split the long list to many smaller lists to run things in parallel or to stagger the running of a script (make sure you only run a script on a smaller batch at a time instead of running it on the full list).
So let's split a long list of host names to 6 pieces. You can do this in many ways, but I use this oneliner:
$NumberOfArrays = 6; $NewArrays = @{}; $i = 0; gc c:\hostlist.txt | %{$NewArrays[$i % $NumberOfArrays] += @($_); $i++}; $NewArrays
It uses a few tricks.
The first one is the modulus operator (%) which spits out the reminder part when you divide two integers. That will mean if you do a mod operation on a running number (from 1 to the max count of the list) with the number you want to split the list to, it will repeat a sequence of numbers. From 0 until it reaches the number one less than the number we are dividing by. Like this:
Lift of index numbers we can use for splitting the list |
The second trick is that this generated repeating sequence of numbers are used as index (or keys) of a hash table. Each key in the hash table has a value, in this case the values of keys will the split list of hosts. The hash table here is $NewArrays which has the arrays of the smaller lists:
The list of smaller lists (arrays) in a hash table |
Note the 'Name' column which is the list of keys.
19 February, 2017
Random password generator - Scripting
These days everything in shops are handcrafted. It's fashionable to buy handcrafted yogurt, honey, jam...beer... why not have your very own homemade password generator?!
It's actually useful to put into scripts which create user accounts in e.g. Active Directory. You can have the given user phone the support team to reset the password when they want to start using the account - so no one knows the password until then.
You really want long (15 characters long) complex passwords because you never know how long a newly created account will sit around waiting for the user to reset the password of it. You can read many books about it why complex and random passwords are needed.
I tell you the whole code upfront and then explain the bits, like in every Columbo episode, they show you the buildup and the murder and then Columbo solves the puzzle in front of your eyes.... Peter Falk was awesome in that character!
The random password generator itself:
[string]::join("",((48..57) + (65..90) + (97..122) | Get-Random -count 15 | %{[char]$_}))
Then pipe these through to a foreach loop and convert the numbers to characters:
%{[char]$_}
Good, we have a random list of characters in an array, but I need it in one long string so I can use it as a real password. For this purpose, we can use the join function of the [string] type with no delimiter:
[string]::join("",...
There you go, you now have your handcrafted random password generator.
It's actually useful to put into scripts which create user accounts in e.g. Active Directory. You can have the given user phone the support team to reset the password when they want to start using the account - so no one knows the password until then.
You really want long (15 characters long) complex passwords because you never know how long a newly created account will sit around waiting for the user to reset the password of it. You can read many books about it why complex and random passwords are needed.
I tell you the whole code upfront and then explain the bits, like in every Columbo episode, they show you the buildup and the murder and then Columbo solves the puzzle in front of your eyes.... Peter Falk was awesome in that character!
The random password generator itself:
[string]::join("",((48..57) + (65..90) + (97..122) | Get-Random -count 15 | %{[char]$_}))
The bits of the oneliner
Passwords need characters, the easiest way to generate some is from the ASCII table. In Powershell you can generate a list of numbers on the fly when you define an array, these can be the ASCII codes of characters:- ASCII codes for all lowercase letters: (97..122)
- ASCII codes for all uppercase letters: (65..90)
- ASCII codes for all numbers from 0 to 9: (48..57)
You can see there are gaps in the list of ASCII codes of lowercase, uppercase letters and numbers. To make these 3 arrays of numbers look like one array, just add them up together:
(48..57) + (65..90) + (97..122)
Array of ASCII numbers for random password |
Nice, we have a list of ASCII codes for all characters we want to chose from, let's pick random ones, 15 of them:
Get-Random -count 15
Then pipe these through to a foreach loop and convert the numbers to characters:
%{[char]$_}
Taking 15 random numbers of the ASCII arrays and converting to characters |
Good, we have a random list of characters in an array, but I need it in one long string so I can use it as a real password. For this purpose, we can use the join function of the [string] type with no delimiter:
[string]::join("",...
There you go, you now have your handcrafted random password generator.
28 January, 2017
Parse user name from events - OS
Have you ever needed to parse fields or text from Windows Event Log? Even if the answers is no, read on. You never know when someone stops you on the road and holds a gun to your head and shouts until you parse all the fields from those 100 000 events.
My example was just about parsing user names from events in the Application log in a very friendly situation - no guns and shouting involved...
There were 300 000+ of these events in the last 24 hours and I had to find out from them the list of users with connection to the app and a connection count for each user in the same time frame.
This is how the events I'm parsing look like:
$timelimit=(Get-Date).addhours(-24);
Get-WinEvent -ea SilentlyContinue -FilterHashtable @{LogName="Application"; StartTime=$timelimit; id=998}
The events will tell me the SID (Security IDentifier) of the user who generated the event, to look up the user name I used the SecurityIdentifier and then the NTAccount classes of System.Security.Principal:
Select @{e={(New-Object System.Security.Principal.SecurityIdentifier($_.userID)).Translate([System.Security.Principal.NTAccount])}; l="User";}
The trick to make this a one-liner was to generate the user name on the fly when creating a custom object with 'Select' in the command
Full command and the measure of run time:
measure-command {$timelimit=(Get-Date).addhours(-24); $tmp=Get-WinEvent -ea SilentlyContinue -FilterHashtable @{LogName="Application"; StartTime=$timelimit; id=998} | Select @{e={(New-Object System.Security.Principal.SecurityIdentifier($_.userID)).Translate([System.Security.Principal.NTAccount])}; l="User";} | group user}
Days : 0
Hours : 0
Minutes : 41
Seconds : 0
Milliseconds : 11
Well, 41 minutes doesn't look like a lot of time when you are spending with your loved ones, but it's way above the limit of the patience of an average IT guy... Remember the first part of this article? I was lucky enough with this application that it logged the user name into the message of the event (with some other random text as well).
So let's try something different:
measure-command {$timelimit=(Get-Date).addhours(-24); $tmp=Get-WinEvent -ea SilentlyContinue -FilterHashtable @{LogName="Application"; StartTime=$timelimit; id=998} | Select @{e={$matches=$null;$a=($_.message -match "UserName: (?<users>[^`n]+)"); $matches.user}; l="User";} | group user}
Days : 0
Hours : 0
Minutes : 8
Seconds : 13
Milliseconds : 54
Very nice, 80% reduction on run time. What's different on this run?
The reason why this is quicker because the command does not read into Active Directory to look up a SID and translate it to user name, instead, it just parses the user name from the message field of the event with a regex pattern:
$matches=$null;$a=($_.message -match "UserName: (?<users>[^`n]+)"); $matches.user
Hope this helps saving a bit of time for some of you and can spend this away from the computer thinking about the next powershell trick!
t
My example was just about parsing user names from events in the Application log in a very friendly situation - no guns and shouting involved...
There were 300 000+ of these events in the last 24 hours and I had to find out from them the list of users with connection to the app and a connection count for each user in the same time frame.
This is how the events I'm parsing look like:
Event with User name to parse and count |
First attempt
I used Get-WinEvent and filtered in its hash based switch as much as possible to speed up the query for events. Something like:$timelimit=(Get-Date).addhours(-24);
Get-WinEvent -ea SilentlyContinue -FilterHashtable @{LogName="Application"; StartTime=$timelimit; id=998}
The events will tell me the SID (Security IDentifier) of the user who generated the event, to look up the user name I used the SecurityIdentifier and then the NTAccount classes of System.Security.Principal:
Select @{e={(New-Object System.Security.Principal.SecurityIdentifier($_.userID)).Translate([System.Security.Principal.NTAccount])}; l="User";}
The trick to make this a one-liner was to generate the user name on the fly when creating a custom object with 'Select' in the command
Full command and the measure of run time:
measure-command {$timelimit=(Get-Date).addhours(-24); $tmp=Get-WinEvent -ea SilentlyContinue -FilterHashtable @{LogName="Application"; StartTime=$timelimit; id=998} | Select @{e={(New-Object System.Security.Principal.SecurityIdentifier($_.userID)).Translate([System.Security.Principal.NTAccount])}; l="User";} | group user}
Days : 0
Hours : 0
Minutes : 41
Seconds : 0
Milliseconds : 11
Second attempt - with regex parsing
Well, 41 minutes doesn't look like a lot of time when you are spending with your loved ones, but it's way above the limit of the patience of an average IT guy... Remember the first part of this article? I was lucky enough with this application that it logged the user name into the message of the event (with some other random text as well).
So let's try something different:
measure-command {$timelimit=(Get-Date).addhours(-24); $tmp=Get-WinEvent -ea SilentlyContinue -FilterHashtable @{LogName="Application"; StartTime=$timelimit; id=998} | Select @{e={$matches=$null;$a=($_.message -match "UserName: (?<users>[^`n]+)"); $matches.user}; l="User";} | group user}
Days : 0
Hours : 0
Minutes : 8
Seconds : 13
Milliseconds : 54
Very nice, 80% reduction on run time. What's different on this run?
The reason why this is quicker because the command does not read into Active Directory to look up a SID and translate it to user name, instead, it just parses the user name from the message field of the event with a regex pattern:
$matches=$null;$a=($_.message -match "UserName: (?<users>[^`n]+)"); $matches.user
Hope this helps saving a bit of time for some of you and can spend this away from the computer thinking about the next powershell trick!
t
Labels:
eventlog,
get-winevent,
match,
OS,
parse events,
powershell,
regex
Subscribe to:
Posts (Atom)