I am writing a powershell script to deploy a .NET 4 Windows Service to a 2008 server via a generated MSI. A fresh installation runs fine, but when I rerun and it tries to uninstall it, the script hangs while trying to do the uninstall. I call msiexec, which runs on the target machine (I can see the process started when the uninstall is running). The only differences between the uninstall and install code is the log name and the /x command passed to msiexec.
Here is the code that I have:
function UninstallService ($serverName, $fileName)
{
write "Start uninstall service."
$msiNamePath = "C:\MsiDeployment\" + $fileName
$processArgs = #("/i", $msiNamePath, "/x", "/qn", "/norestart", "/l", "c:\msiuninstall.log")
# Create session
$session = New-PSSession -ComputerName $serverName
# Enter session
Enter-PSSession $session
# Do uninstall
Invoke-Command -Session $session -ScriptBlock { param($pArgs,$rootDir) Start-Process -FilePath "$rootDir\msiexec.exe" -ArgumentList $pArgs -Wait } -Args $processArgs,("$env:systemroot\system32")
# Close session
Exit-PSSession
Remove-PSSession $session
if (!$?) { throw "Could not uninstall the service remotely on machine " + $serverName }
write "End uninstall service."
}
If I terminate the running msiexec on the server, the script continues on processing (fails later due to checking if the service is uninstalled). I'm guessing that there is some prompt that is looking for user input (maybe UAC), but I'm not entirely certain. I get no log file on the uninstall, but install writes the log file.
Enter-PSSession is meant only for interactive use and not in a script. So, essentially, your script stops working after Enter-PSSession $session.
Remove the following lines from your script and everything should work as expected.
# Enter session
Enter-PSSession $session
# Close session
Exit-PSSession
All you need is the Invoke-Command.
Actually, I figured out the issue. I left the /i flag in the arguments when it should have just been the /x flag. Works fine now.
The flag was the msiexec throwing an error page even when the qn flag was passed to it. Not sure if it should have done that.
Related
Ok, So I am working on the powershell remote deployment of software to ADUsers.
I have remote access to the computer and all, no problems there.
Right now I am at the point where I have a exe file from Ninite to just install 7zip onto the client pc just to see when it works so i can start deploying some bigger programs to it.
The guide I have found to help me deploy out the software for now looks like this:
Invoke-Command -ComputerName *REDACTED* -Scriptblock {
Start-Process C:\Users\Administrator\Documents\ninite_7_zip\Ninite 7Zip Installer.exe '/silent' -wait
}
When I do run this code, I get the error:
A positional parameter cannot be found that accepts argument 'Installer.exe'.
So I thought to myself, that it might be because of the spaces in the name. So therefore I changed it to:
Invoke-Command -ComputerName *REDACTED* -Scriptblock {
Start-Process C:\Users\Administrator\Documents\ninite_7_zip\Ninite_7Zip_Installer.exe '/silent' -wait
}
And ofcourse also changed it's name within the folder to match the "newly made" code.
But the error now changed into:
This command cannot be run due to the error: The system cannot find the file specified
Even though I use Powershell ISE, and I used it's guideboxes when writing, to enter the folder and find it, when I wrote the directory.
My only goal in this, is that I want to remotely run and complete this installer on the client PC when deployed from the DC upon which the file lies.
Anybody got a qualified guess? Or maybe even so, a solution.
Thanks in advance for your kind answers.
When I do run this code, I get the error:
A positional parameter cannot be found that accepts argument 'Installer.exe'.
You'll want to use quotation marks to qualify path names with spaces in them:
Start-Process 'C:\Users\Administrator\Documents\ninite_7_zip\Ninite 7Zip Installer.exe' '/silent' -wait
But the error now changed into:
This command cannot be run due to the error: The system cannot find the file specified
Even though I use Powershell ISE, and I used it's guideboxes when writing, to enter the folder and find it, when I wrote the directory.
ISE is not smart enough to realize that the scriptblock is to be executed on a remote computer, so it completes the path based on your local file system.
You still need to copy the executable to the remote machine in order to execute it:
# first copy the installer to remote file system
$remoteSession = New-PSSession -ComputerName $computerName
$localInstaller = 'C:\Users\Administrator\Documents\ninite_7_zip\Ninite 7Zip Installer.exe'
$remotePath = Invoke-Command -Session $remoteSession -ScriptBlock { $env:TEMP }
Copy-Item $localInstaller -Destination (Join-Path $remotePath "7zInstaller.exe") -ToSession $remoteSession
# now we can invoke the executable on the remote machine (re-using the same remoting session)
Invoke-Command -Session $remoteSession -ScriptBlock {
Start-Process (Join-Path $env:TEMP "7zInstaller.exe") '/silent' -Wait
}
# clean up
$remoteSession |Remove-PSSession |Out-Null
I am trying to upgrade a server with a particular application from a client by PowerShell remote:
Invoke-Command -ComputerName $server -Credential $mycreds {Start-Process -FilePath "C:\temp\xxx.exe" -ArgumentList "-default", "-acceptEULA" -wait }
Whatever I try, I get messages like "Can't find the file specified..." what do I do wrong?
FilePath is on the local (client) computer.
Your C:\temp\xxx.exe executable must be present on the server (the remote machine) for your command to work, because that is where your script block ({ ... }) executes.
Note: By contrast, if you use Invoke-Command with the -FilePath parameter in order to run a locally present script file (.ps1) remotely, PowerShell automatically copies it to the remote machine; from the docs: "When you use this parameter, PowerShell converts the contents of the specified script file to a script block, transmits the script block to the remote computer, and runs it on the remote computer."
To copy the executable there from your local (client-side) machine, you need a 4-step approach (PSv5+, due to use of Copy-Item -ToSession[1]):
Create a remoting session to $server explicitly, using New-PSSession
Copy the local (client-side) executable to that session (the remote computer) with Copy-Item and its -ToSession parameter
Run your Invoke-Command command with the -Session parameter (rather than -ComputerName) in order to run in the explicitly created session (this isn't strictly necessary, but there's no need to create another (ad hoc) session).
Run Remove-PSSession to close the remote session.
Important: In a PowerShell remoting session, you cannot run external programs that require interactive user input:
While you can launch GUI applications, they invariably run invisibly.
Similarly, interactive console applications aren't supported (although output from console applications is received by the client).
However, interactive prompts from PowerShell commands are supported.
To put it all together:
# Specify the target server(s)
$server = 'w764' # '.'
# Establish a remoting session with the target server(s).
$session = New-PSSession -ComputerName $server
# Copy the local executable to the remote machine.
# Note: Make sure that the target directory exists on the remote machine.
Copy-Item C:\temp\xxx.exe -ToSession $session -Destination C:\temp
# Now invoke the excutable on the remote machine.
Invoke-Command -Session $session {
# Invoke *synchronously*, with -Wait.
# Note: If the program is a *console* application,
# you can just invoke it *directly* - no need for Start-Process.
Start-Process -Wait -FilePath C:\temp\xxx.exe -ArgumentList "-default", "-acceptEULA"
}
# Close the remote session.
# Note: This will terminate any programs that still
# run in the remote session, if any.
Remove-PSSession $session
[1] If you're running Powershell v4 or below, consider downloading psexec.
I am having an issue with running a batch file that is located on a remote server.
I have a batch file located on a remote server that i want to run that kicks off an automated Selenium test. For simplicity, let's say the name of my batch file is mybatch.bat
I have the following code in a Powershell script located on the server:
$BatchFile = "mybatch.bat"
Start-Process -FilePath $BatchFile -Wait -Verb RunAs
If I run this PowerShell script locally on the server in ISE then it runs fine and it kicks off the selenium test which takes a couple minutes to run.
Now I want to try to execute this test from another machine by using PowerShell remoting. Let's assume that remoting is already configured on the servers.
I have a PowerShell script located on another server which has the following code segment. Assume that all of the session variables have the correct information set:
$CMD = "D:\mybatch.bat"
$TargetSession = New-PSSession -ComputerName $FullComputerName -Credential $myCreds -ConfigurationName RemoteExecution
$command = "powershell.exe -File $CMD -Wait"
Invoke-Command -Session $TargetSession -ScriptBlock { $command }
When this script runs, it does connect to the remote machine and create a remote session. It does look like it kicks off the batch file because PowerShell does not give me an error. But it does not wait for the full 3 or 4 minutes for the Selenium test to finish. It seems like it just times out. Also if I am logged onto the other machine, I don't see any Selenium web test running. No Selenium log files or results files are created on remote server as should be expected.
I was wondering what I could be doing wrong with my code.
Also, it seems that the server always returns the echo outputs of the batch file to my local machine. I see these random blinking white screen on ISE which looks like output from the batch file
$command = "powershell.exe -File $CMD -Wait"
Invoke-Command -Session $TargetSession -ScriptBlock { $command }
There are 2 issues with the above code:
$command inside the scriptblock and $command outside the scriptblock are different variables due to different scopes. The variable inside the scriptblock is thus undefined and the scriptblock will simply echo an emtpy value.
Even if $command weren't undefined, the scriptblock would still just echo its value, since it's defined as a string. PowerShell does not execute strings unless you're using something like Invoke-Expression (which you shouldn't).
This should do what you want:
$CMD = "D:\mybatch.bat"
$TargetSession = New-PSSession -ComputerName $FullComputerName -Credential $myCreds -ConfigurationName RemoteExecution
Invoke-Command -Session $TargetSession -ScriptBlock { & $using:CMD }
If you would like execute a bat file from another machine by using PowerShell Remote Session, simply enter dot and then follow by a whitespace, then enter the exact path of bat file located on that remote machine.
Example
Invoke-Command -ComputerName RemoteComputerName -Credential $credential -ScriptBlock {. "D:\Temp\Install_Something.bat"}
I have a script that installs an .exe with some arguments remotely to a list of servers that works fine. When I try to do almost the exact same thing but run the uninstall.exe that gets installed to C:\Program Files (x86)\ it won't work.
When I run the scripts on the server locally, it kicks off the uninstall. When I try to run the exact same script or command using the powershell invoke-command it won't work.
$serverlist = Get-Content -Path C:\NagiosInstall\test.txt
ForEach ($server in $serverlist) {
New-Item -Path "\\$server\C$\" -Name "NagiosInstall" -Force -ItemType "directory"
Copy C:\NagiosInstall\ncpa-2.1.6.exe \\$server\C$\NagiosInstall\ncpa-2.1.6.exe
Copy C:\NagiosInstall\install.bat \\$server\C$\NagiosInstall\install.bat
invoke-command -ComputerName $server -ScriptBlock {C:\NagiosInstall\install.bat}
Start-Sleep -s 15
invoke-Command -ComputerName $server -ScriptBlock {Remove-Item -LiteralPath "C:\NagiosInstall" -Force -Recurse}
}
The install .bat is just a simple command to silently install that ncpa-2.1.6.exe.
Above is my install script, that part all works fine.
invoke-command -ComputerName $server -ScriptBlock {Start-Process -FilePath "C:\Program Files (x86)\Nagios\NCPA\uninstall.exe" -ArgumentList "/S"}
Running the above command, nothing happens. No errors, nothing.
& "C:\Program Files (x86)\Nagios\NCPA\uninstall.exe" -ArgumentList "/S"
But running the above command in powershell that's running as admin locally on the server and it works just fine.
I've also tried the same approach to create and copy and run a batch file, very similar to the above "install" code. Same thing... nothing happens but if you run the batch locally on the server, it works just fine. I can post this code if anyone is interested.
I'm guessing it has to do with the invoke-command or the fact that it's in C:\Program Files (x86) which might make the syntax different, but I've tried many things and I'm out of ideas besides making an account and posting here.
The issue is that Invoke-Command runs non-interactively, and therefore cannot run as Administrator and respond to a UAC prompt.
The only workaround is to connect to the computer via a PSSession with credentials, and execute it that way:
$Cred = Get-Credential
$Session = New-PSSession -ComputerName $server -Credential $Cred
Invoke-Command -Session $Session -ScriptBlock {Start-Process -FilePath "C:\Program Files (x86)\Nagios\NCPA\uninstall.exe" -ArgumentList "/S"}
$Session | Exit-PSSession
Edit:
The reason that the installer works is that the UAC prompt for Windows Installs is different than anything else in Windows see: How to Silence the UAC Prompt for Per-Machine MSI Packages for Non-Admins or Using Windows Installer with UAC.
Essentially, Windows Installer (already running as admin and UAC approved), is what runs the install on your behalf, and it is Windows Installer and installer settings that determines if you need to see a UAC prompt or not. Hence, this is why the install works. Windows Installer determined that you did not need to see the UAC prompt, and the install proceeds.
Uninstalling is different. Since you are running uninstall.exe, the executable needs admin access and Windows will do UAC before the uninstall.exe even runs.
I found the Invoke-Command cmdlet in PowerShell that supposedly is invoking a command on another server but this doesn't quite work. Instead what I get is the print of what the bat/exe. My exe is a console app and the bat was a test I did that launches the exe with start "" "myexe.exe".
This is the command I'm trying to do:
Invoke-Command -ComputerName 10.10.10.10 -ScriptBlock {
'C:\Program Files\program.exe'
}
or
Invoke-Command -ComputerName 10.10.10.10 -ScriptBlock {
'C:\Program Files\batch.bat'
}
In both cases instead of the command getting invoked on the other server I get the print on the server I call from.
Did I miss an argument somewhere? I want to launch the exe/bat on the remote server, not on the server I'm on.
EDIT
I made it work with this:
$command = "PathtoExe.exe"
$process = [WMICLASS]"\\10.10.10.10\ROOT\CIMV2:win32_process"
$result = $process.Create($command)
But now the exe is not displayed, it's like running in the background.
Your problem is how you're trying to call the executable. In PowerShell, everything is an object. What you're doing is printing the String to the console instead of executing. To invoke the string, you need to use the call operator: &
Invoke-Command -ComputerName 10.10.10.10 -ScriptBlock {
& 'C:\Program Files\batch.bat'
}
This will cause it to use the default program for that extension.
I had the same pain trying to run "netsh.exe" on a remote host. Eventually got it working with the following code:
Invoke-Command -ComputerName 10.10.10.10 -ScriptBlock {Invoke-Expression "C:\Program Files\program.exe"}