- Create a certificate-signed RDP shortcut via Group Policy - Fri, Aug 9 2019
- Monitor web server uptime with a PowerShell script - Tue, Aug 6 2019
- How to build a PowerShell inventory script for Windows Servers - Fri, Aug 2 2019
PowerShell never ceases to amaze me with its flexibility. One reason I love it so much is that it can do just about anything you'd like. If you're good enough, PowerShell code can replace just about any piece of software you have. I might argue, though, that just because you can doesn't mean you should. However, there are times when you need a simple solution to get a job done. One of those jobs is software deployment.
I come from a Microsoft System Center Configuration Manager (SCCM) background. SCCM is expensive; it’s a huge product that does a lot of stuff. Software deployment is one of its features. SCCM is for large software deployments, and I always thought it was overkill for small deployments of fewer than a dozen computers. That's why I sometimes used good ol' PowerShell to do the job. Let's go into how to do that.
I'm going to assume you've already figured out how to install the software silently. Depending on the installer type, you're probably using Windows Installer, InstallShield, or perhaps some other homegrown installer. Regardless, test the install, get it working locally, and then you can look into deploying it remotely.
The next step will require PowerShell remoting. You'll need to ensure the appropriate firewall ports are open and that you have a WinRM listener configured on each computer. For the super-quick way, just run 'winrm quickconfig' on each computer and it will do the rest. I'm also going to assume that your computers are in a domain. This technique is still usable for computers in a workgroup but requires further tweaking to make WinRM work.
Next, you should have a shared folder on your network that contains one folder for each piece of software you'd like to deploy. Here I have folders called SomeClient and vnc representing all the files necessary to install each piece of software.
Now that we have a source repository set up, we need a list of computers to target. For simplicity, I'll use a text file, but as long as you're able to pull a list of computer names from somewhere like Active Directory or some other database, that works too.
I have three PCs I'd like to deploy software to, as you can see.
Next, we'll copy each of the software folders to each of the clients. I prefer copying the entire folder (temporarily) to the client to remove any network hiccups that might occur during install. To do this, I'll use Copy-Item to copy each folder to a temporary location on each client.
$computers = Get-Content –Path C:\Computers.txt foreach ($pc in $computers) { Get-ChildItem \\MEMBERSRV1\Software -Recurse | Copy-Item \\$pc\c$\Windows\Temp -Force }
This will create a C:\Windows\Temp\vnc and a C:\Windows\Temp\SomeClient folder on each computer.
We now need to execute the installer on each computer and pass it the correct arguments. I'm going to assume each piece of software is an MSI file, the MSI is called install.msi, and each installs silently with the following syntax locally.
Msiexec /I install.msi /qn
With this, I can execute the command on each computer by using PowerShell to issue the remote command with the Invoke-Command cmdlet inside the loop.
$softwareFolders = Get-ChildItem \\MEMBERSRV1\Software -Directory foreach ($pc in $computers) { foreach ($sw in $softwareFolders) { Copy-Item –Path $sw.FullName -Destination \\$pc\c$\Windows\Temp -Force -Recurse Invoke-Command –ComputerName $pc –ScriptBlock {msiexec /I "C:\Windows\Temp\$($using:sw.Name)\install.msi" /qn} } }
Notice that I had to add some code to account for the software folder names. This was because I needed a way to reference the folder name inside of the scriptblock that Invoke-Command uses. This will ensure install.msi executes on each computer for each piece of software.
Finally, the only thing left to do is to clean up our temporary mess.
Subscribe to 4sysops newsletter!
$softwareFolders = Get-ChildItem \\MEMBERSRV1\Software -Directory foreach ($pc in $computers) { foreach ($sw in $softwareFolders) { Copy-Item -Path $sw.FullName -Destination "\\$pc\c$\Windows\Temp" -Force -Recurse Invoke-Command –ComputerName $pc –ScriptBlock {msiexec /I "C:\Windows\Temp\$($using:sw.Name)\install.msi" /qn} } Remove-Item -Path "\\$pc\c$\Windows\Temp" -Recurse -Force }
Nice job. But is there a way to retrieve status codes for the installation? Just to know if the installation went fine, wrong or have a reboot pending?
Thanks!!
Yes. You can use $LastExitCode or you could use the ResultCode property from Start-Process.
“Adam the Automator”. Deltron 3030 fan?
what if the remote machines has username and password. The same username and password for all the remote machines. Can you please tell me how to include that in the code
Adithya: When you use your Invoke-Command, you can pass it a Credential object. So, create your credential with just the username & password, and it should work.
If you really want to *completely* automate it, you could look at using the Credential Store on the system you are going to invoke the script from. You’d log in as the account that will run the script, and store the password in the credential store (it is encrypted for that user only on that computer only). Then your script could retrieve that stored credential information to create the Credential Object.
David F.
Thank you very much for your reply. Can you please tell me how can add it to the code. Just a code and where should i place it. It will be really helpful to me.
Sure.. 🙂
Line 2 – get the credential
Line 6 – add the -Credential parameter that’s it..
David F
Thank you very much David. I truly appreciate your help.
Will give a try and let you know 🙂
$softwareFolders = Get-ChildItem \\MEMBERSRV1\Software -Directory
$MyCredential = Get-Credential $user=”adithya” $pass=”abc@123″—– Is this the correct way to give the credentials
foreach ($pc in $computers) {
foreach ($sw in $softwareFolders) {
Copy-Item -Path $sw.FullName -Destination “\\$pc\c$\Windows\Temp” -Force -Recurse
Invoke-Command –ComputerName $pc –ScriptBlock {msiexec /I “C:\Windows\Temp\$($using:sw.Name)\install.msi” /qn} -Credential $MyCredential
}
Remove-Item -Path “\\$pc\c$\Windows\Temp” -Recurse -Force
}
Get-Credential normally pops up a GUI dialog asking for the credentials. This is where things can get messy..
Normal best practice is to never put the credentials in the script in any form, and of course, that’s frequently not realistic.
Now.. let’s say your circumstances say that it is ok to have the creds in the script.. then you could do this:
I have started working with some “new” modules/capabilities that should give you a *much* better solution, but it requires that you store the credentials using whatever account is going to run the script..
https://gallery.technet.microsoft.com/Manipulate-credentials-in-58e0f761
So, you use this module to store the credentials, and then in the script you use the same module to retrieve the credentials from the vault, and then convert them from the security type credential to a pscredential (that function is already in the module). So, using this you can have your secured credential under your account to run the script, and use it that way.
This module has been the best way I have found to manage credentials for scripts. The downside being that your system needs to be at least Win8/2012.
David F.
Hi Adam,
Everything works except the install.
The files are copied over to the temp directory but the app (adobe reader) doesn’t get installed.
Powershell doesn’t report any errors.
Any suggestions?
@Rick
Can you share the script?
Yes sir but I really didn’t change the original script. I just did a copy paste.
Thank you for your help.
I commented out the “Remove-Item” line because I was receiving the error:
Remove-Item : Access to the path ‘\\Test66\C$\Documents and Settings’ is
denied.
At line:11 char:4
+ Remove-Item -Path “\\$pc\C$” -Recurse -Force……
@Rick
Fortunately, you get an access denied error for the Remove-Item line,
because your line is removing everything which is under C:\ from the remote computer.
Then to come back to your installation line, you are using C$ instead of C: in line 9.
Another problem is that the original code has been designed to enumerate a list of folders containing an MSI.
You are instead enumerating the files in that folder.
By replacing the C$ in line 9 and replacing line 3 with the following line it should work.
Well when you put it that way it makes sense 😀…lol!!!
I guess you can tell I’m a newbie.
Thank you for your help and explanation I really appreciate it.
Is anyone willing to help me out? First, I'm not a PowerShell guy. I've kludged together some small scripts here and there, but this is really me trying to learn how to do a specific thing, so I get concepts, but I could be missing some basic stuff.
I'm trying to do exactly what this script is for. Deploy an MSI file to a number of servers. I am running PowerShell on my local machine, launching it with credentials that also have admin rights on each of the servers I am trying to interact with.
I have the source files stored on my local machine.
When I run it, it seems to pause (presumably running) for a time, then it starts generating a ton of errors. For example: Remote-Item : Cannot remove item. \[machinename]c$WindowsTemppdk-SYSTEM…per1516.dll: Access to the path 'perl516.dll' is denied.
Tons of those. It's like I've got something wrong with the folder stuff and it's trying to kill everything off the WindowsTemp folder and not just the folder we copied.
But additionally, the software doesn't install, either.
For testing, I tried this line manually:
Copy-Item c:deploysoftware -Recurse \[machinename]c$windowstemp -Force and it did copy SoftwareFirefox (and the msi).
I then tried:
Invoke-Command -ComputerName [machinename] -ScriptBlock {msiexec /I "c:windowstempsoftwarefirefoxinstaller.msi" /qn}
It pauses for about two seconds and then is back at the command prompt with no error, yet the software does not install on the named server.
I sincerely appreciate any help offered.
Thank
@Steven Newman
First concerning the deletion step:
The following line removes the C:\Windows\Temp folder and everything inside (files and subfolders)
However, keep in mind that this is a system folder.
Windows uses it to store its temporary data.
Thus, when you delete the content, you also try to delete files that Windows has stored there, and which are sometimes still in use.
Now concerning the installation part:
What happens when you execute the following command line on the computer directly?
On the target server, if I remote in and launch powershell and then run
msiexec /I "c:\windows\temp\software\firefox\installer.msi" it runs fine, and the installer pops up and it completes the install.
How would I need to change the script so that rather than trying to empty c:\windows\temp, I was only emptying c:\windows\temp\software? Would I just need to make the last line be:
Remove-Item -Path "\\$pc\c$\Windows\Temp\Software" -Recurse -Force
I would try this.
OK, I assume I'm entering that in PowerShell from my local machine. After I type the line in (replacing machinename with server name and adding backslashes to paths, I hit enter and I get the two >>s
Hehe something went wrong here 🙂 I suggest to request deletion 🙂
Seems you tried to paste an image into the comment field. This doesn't work. I removed the corresponding code from your comment.
@Steven Newman
Yes, as you found it out the following line is the way to go in order to remove your temporary files.
Can you please run the following code and post the result?
Thanks, Luc. So I ran exactly what you had above. It copies the folder and file to the targets, then removes it, but without ever installing.
An additional follow up. If I open a PowerShell session on my local machine, and use the Enter-PSSession [ServerName], and once the connection is established I browse to the windows\temp\firefox\installer.msi file and ran "msiexec /I installer.msi /qn" it did install on the server fine.
If that provides any insight… 🙂
@Steven Newman
Please have a look at the Setup and Application logs when you run the msiexec.
Also, did you try Leos' suggestion with the Start-Process cmdlet and the –Wait parameter?
Just in case the Invoke-Command would return before the msiexec was able to finish its work…
Hi,
Instead of getting from computer.txt can I set it as AD group?
Hi Jim, Yes. You could use the AD group to retrieve the computers and then loop over to perform the installation. Check out below article for retrieving the computers from AD group.
https://4sysops.com/archives/get-adcomputer-display-computers-in-ou-or-ad-group-with-powershell/
Hi Guys,
Can any one help me with a script to remote install windows offline patches (KB's) and get the result if installed or failed
We have 500 servers and patching them manually is very tough.
Can anyone really hep please.
You can share the script here or to my mail
dinakar.tech@gmail.com
Thanks
Hello, i modified the code a bit and it looks like this.
how ever when i run it my output is as follows..
it seems like its creating a directory, but does not copy the file there fore it can not run it.