How do I remotely issue a CLI command to thousands of Windows domain computers at once?
Solution 1:
The General Case:
How do I remotely issue a CLI command to thousands of Windows domain computers at once?
As with most things in our profession, your easiest approach is generally to break the job down into simple, discrete tasks, and then either perform those tasks in sequence, or string them back together (such as by putting them all into a single script).
In this, you have 3 discrete tasks I can establish.
Collect a list of clients to issue the command on.
Connect to all the clients.
Issue the command to all the clients.
After this you'd probably want to verify the results, just like before this, you'd want to test your process, but let's ignore that for the purposes of this question. Just don't ignore pre-implementation and post-implementation testing in the real world, or you'll be sorry.
You also have an "architectural" decision to make (whether to perform steps 2 and 3 in parallel or in serial), but let's ignore that as well. I'll be doing them in serial, because it's easier, and I'm lazy.
Note, at no point so far have I made mention of any specific tools, implementations or given examples. That's deliberate. To this point, the above has all been design and/or architectural work. Yes, if you want to "do it right" you design and plan before you implement. (Go from general to specific.)
To make this practical, below is a specific problem I solved by simultaneously issuing a command to thousands of Windows domain computers.
The Specific Case:
How do I correct the time on all my Windows domain computers at once?
For reasons that don't need going into, I recently encountered a situation where the best solution was to issue a single command to every single computer owned by my employer, in order to fix a domain-wide time skew. (I was also highly time-constrained - had less than 2 hours to have all the computers in the domain back in sync with "true" time.)
-
Collect a list of clients to issue the command on.
- Due to my environment's specifics, coupled with my specific tools and skills, I used PowerShell to do this.
- I have PowerShell deployed to every client in the environment, every client is domain-joined, I deployed a GPO to enable WinRM, for the purposes of PowerShell Remoting on all my clients over a year ago, I like PowerShell and 'm comfortable using it.
- The cmdlet to use here is
Get-ADComputer
, which is used to retrieve a computer object from Active Directory. This is the way to go because all the computers were domain-joined, and therefore listed in AD.
- Because I want all the computers, and because of what I'm going to do next, I want to simply store this list in a variable, so the specific command I'll use is:
-
$boxlist = Get-ADComputer -filter *
(Stores a list of all the computer objects found in Active Directory into the variable named boxlist)
- Due to my environment's specifics, coupled with my specific tools and skills, I used PowerShell to do this.
-
Connect to all the clients.
- Since I'm in a hurry, I'm going to stick with what I know. I routinely use PowerShell Remoting for single computers, or small groups of computers and am very comfortable with it. I haven't used it for a group this large, but there's no reason it shouldn't work, and I have no time to learn new things or install new tools right now, so it will have to do.
-
New-PSSession
is my go-to cmdlet for this.
-
- At this point, I realize I'll need to provide credentials to actually complete the request, chose the
Get-Credential
cmdlet to do that with, and end up with the below two commands as a result. -
$creds = Get-Credential domain\user
(Creates a prompt into which I enter my credentials and stores an authentication token in the variable named creds) -
$session = New-PSSession -ComputerName $boxlist.name -Credential $creds
(Creates new PowerShell sessions for every computer in the boxlist variable, using the credentials I just provided, and stores those PowerShell sessions in the variable named sessions)
- Since I'm in a hurry, I'm going to stick with what I know. I routinely use PowerShell Remoting for single computers, or small groups of computers and am very comfortable with it. I haven't used it for a group this large, but there's no reason it shouldn't work, and I have no time to learn new things or install new tools right now, so it will have to do.
-
Issue the command to all the clients.
- Because of the way time is configured on our domain, and the root cause of the time skew, I already know what command I want to issue. We have a GPO that sets all the Windows Time Service settings the way we like, our authoritative time source is synced up to true time, our subordinate time sources are synced up with the domain's authoritative time source, and what I need to do is just force the clients to immediately resync with their local time source.
-This calls for
w32tm /resync /nowait /rediscover
, and using Powershell, would be invoked with the Invoke-Command cmdlet. -
Invoke-Command -Session $session -ScriptBlock {w32tm /resync /nowait /rediscover}
(Invokes the command fed to the Scriptblock switch to the Session switch, which contains the variable named sessions that has a list of all the computers in my domain in it)
- Because of the way time is configured on our domain, and the root cause of the time skew, I already know what command I want to issue. We have a GPO that sets all the Windows Time Service settings the way we like, our authoritative time source is synced up to true time, our subordinate time sources are synced up with the domain's authoritative time source, and what I need to do is just force the clients to immediately resync with their local time source.
-This calls for
The script, or all its components have been written, and may either be executed, one line at a time in a PowerShell shell, or saved as a PowerShell script file and run. I'm in a hurry, and this is a pretty short script I don't imagine I'll need to use again (or have trouble recreating, so punching it up line-by-line in the shell window is what I end up doing.
$boxlist = Get-ADComputer -filter *
$creds = Get-Credential domain\user
$session = New-PSSession -ComputerName $boxlist.name -Credential $creds
Invoke-Command -Session $session -ScriptBlock {w32tm /resync /nowait /rediscover}