Running a uninstaller as administrator remotely over Powershell - powershell

I am working on a small software deployment script which uninstalls and installs a new version of Check_MK remotely by using powershell.
Everything is working great - i am able to localize the old service, stop it and determine the installation path of it. The problem is to run the "uninstall.exe" of the service.
This can be easily done by using a Powershell session logging on the server with credential parameter in use.
$passwd = convertto-securestring -AsPlainText -Force -String "password"
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist "domain\user",$passwd
$session = new-pssession -computername "192.xxx.xxx.xxx" -credential $cred
Enter-PSSession $session
The problem is you can't use PSSession in a script - it is made for personal use and not automation.
So i tried to use Powershells Invoke-Command cmdlet.
Invoke-Command -Computername "192.xxx.xxx.xxx" -credential $cred -argumentlist $cred -ScriptBlock {
# Lots of stuff and enter uninstall directory
uninstall.exe /S
}
Calling the exe file ends in a query for administrator credentials.
I also tried to remote start a elevated Powershell session and use it for uninstallation..
Start-Process powershell -Credential $cred
Executing a script located on the server for starting uninstall.exe using the elevated Powershell session ends in a UAC query (not querying for credentials, but asking for executing).
Is there any other solution to handle this? I tried a lot more, but nothing worked.

Here's another approach, using Schedule Tasks.
Create a task locally using the logged-on user credentials, you must have admin rights on the remote computer of course.
Function Run-RemoteCommand
{
Param(
[Parameter(Mandatory = $true)]
[String]
$Computer,
[Parameter(Mandatory = $true)]
[String]
$Task,
[Switch]
$Background
)
$TaskName = "TempTask"
$Explorer=gwmi win32_process -ComputerName $Computer | ? {$_.ProcessName -eq "explorer.exe" }
$OWner = $Explorer.GetOwner().Domain + "\" + $Explorer.GetOwner().User
if ($Background)
{
$Create = "SCHTASKS /Create /TN ""$TaskName"" /RU System /TR ""$Task"" /SC Once /ST 22:30"
}
Else
{
$Create = "SCHTASKS /Create /TN ""$TaskName"" /RU $Owner /TR ""$Task"" /SC Once /ST 22:30"
}
$Runit = "SCHTASKS /Run /TN ""$TaskName"""
$Delete = "SCHTASKS /Delete /TN ""$TaskName"" /F"
$WMI = Get-WmiObject -List Win32_Process -ComputerName $Computer
$WMI.Create($Create)
$WMI.Create($Runit)
Sleep 2
$WMI.Create($Delete)
}
To Run it:
Run-RemoteCommand -Computer "192.xxx.xxx.xxx" -Task 'c:\path\uninstall.exe /S'
if you want to run it as 'NT AUTHORITY\SYSTEM' add the -Background Switch

The best way would be to always have the local shell run as an elevated user. This allows you to start invoke-command without providing credentials in a script.
Once you have a Powershell running as an administrative user, and you can run your script using invoke-command. I do it all the time.
If you are scheduling this task, you will have to have the task run as the elevated user. This again, stops you from having to put the password in plain text in the script.

You don't mention if this is in a domain context or not.
If it is in a domain context, you could use a startup script in a Group Policy Object to accomplish your goal. It will execute on the next reboot of the workstation.
You can also do it without a group policy, but it means visiting each computer individually and changing the local group policy object - at that point, you might as well just run the powershell script.
Another solution: you could create a .zap file and deploy that using Group Policy's software deployment functionality. This would be a bit of an abuse of the system. .zap files are meant to install software that isn't packaged in MSI files, but you could also use it to launch a script that uninstalls your software. Ordinarily, .zap files cannot be uninstalled.
A third option: download Chocolatey from http://www.chocolatey.org. This is a PowerShell script that can manage the installation of a lot of standard software for you. Configure it on each workstation to run automatically (using the scheduler or so). Create a NuGet package for your software (NuGet is what Chocolatey runs under the hood), and use chocolatey to deploy or undeploy your software as needed.
As an aside, instead of rolling your own installer, you may want to consider converting it to an MSI file - those can easily be installed and uninstalled, even remotely, without excessive UAC hassle. You could also use a NullSoft installer, but those are harder to automate remotely. MSI is Microsoft's official installation method.
Some people create MSI wrappers around standard .exe-type installers, too.

Related

Looking to map an Azure Fileshare as a mapped network drive on an Azure Windows VM via another machine/Custom Script Execution

I'm attempting to provision a Windows VM and I need to map some Azure fileshares to drives for the VM user that will be interacting with the VM.
I've been trying to make "az vm extension set"/Custom Script Execution work for me by calling some PowerShell scripts to setup the mapping to the fileshare, but since the process runs as NT AUTHORITY\SYSTEM, the mappings aren't working, obviously. I've tried to switch user contexts in my scripts via having an intermediate script that changes context to my VM user and then calling another script that does the work, but that doesn't seem to be working.
$scriptFile = $args[0]
$username = $args[1]
$password = $args[2]
$securePassword = ConvertTo-SecureString $password -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential $username,
$securePassword
Start-Process Powershell.exe -Credential $credential $scriptFile
Unfortunately it seems nothing gets run in the $scriptFile that I call, and I can't get any errors out of standard out/err, so I'm at a loss as to how this can be done.
Certainly someone out there has had to run scripts as another user via the Custom Script Execution method before, I'm hoping they happen to read this post.
Is there a way to set what user the Custom Script Execution runs as?
No, there is no way of setting a user under which script extension runs.
You also should use -PassThru and -Wait and\or -RedirectStandardError\-RedirectStandardInput to your command invocation. Also, add -ErrorAction Stop to your commands to propagate errors.

Elevate creditals with powershell via Local System Account

I want to deploy code using powershell via Jenkins Job. This works fine in the powershell ise.
$username = "mydomain\builder"
$password = "notmypassword"
$credentials = New-Object System.Management.Automation.PSCredential -ArgumentList #($username,(ConvertTo-SecureString -String $password -AsPlainText -Force))
$Arguments = "-ExecutionPolicy Bypass -File C:\Test.ps1 -NoNewWindow -WorkingDirectory C:\Windows\System32\WindowsPowerShell\v1.0 -NoLogo -NonInteractive"
Start-Process "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -Credential $credentials -ArgumentList $Arguments
But when I run it from Jenkins which use the local system I get the following error message.
Start-Process : This command cannot be run due to the error: Access is denied.
At C:\WINDOWS\TEMP\hudson5557889306949142167.ps1:7 char:1
+ Start-Process powershell.exe -Credential $credentials -ArgumentList $
If change I change the Jenkins service to another account it works. Why won't elevated permission work under the local system account?
note: the only code in test.ps1 is New-Item c:\scripts\new_file.txt
There seems to be a restriction on certain commands when a script is run under LocalSystem. This makes sense in terms of security, given that LocalSystem:
has complete unrestricted access to local resources. This is also the disadvantage of LocalSystem because a LocalSystem service can do things that would bring down the entire system.
Reference: MSDN, The LocalSystem Account
There is a similar question at SuperUser: Can not create process with elevated permissions from LocalSystem account with no answer so far a reference to this answer now.
There is a similar question at TechNet: Runing PowerShell script with the permissions of the LocalSystem user with answers suggesting to run the script via Task Scheduler.
I can think of using runas with /savecred and a /user:... with appropriate permissions whose password never expires. AFAIR you have to invoke runas with /savecred interactively once, enter the credentials and it will take the saved credentials from the next invocation onwards.

How do I execute a powershell script under a specified credential without a prompt?

I'm writing an 'Action Script' in VMWare AppDirector 'AppD' which installs MS Dynamics. (My action script is actually a powershell script). The way this works is that AppD will execute a powershell script on a newly deployed server, using a builtin administrator account. This script is one of the last steps in a highly orchestrated deployment. At this stage my SQL server has been deployed, the databases loaded, and I'm performing the final deployment.
When I run my script logged in as myself, everything works great. But of course that's executing under 'mydomain\myusername' which has access to the SQL server etc. However, when AppD executes this script under a local builtin account, it doesn't have the credentials needed by setup to authenticate against SQL, and make proper connections for install to succeed.
My first attempt was to just call a script, that invokes my actual deployment script, so I can pass credentials;
$user = "mydomain\myusername"
$pword = ConvertTo-SecureString -String "mypassword" -AsPlainText -Force
$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $User, $pword
Invoke-Command -FilePath "C:\Scripts\DeployAOS.ps1" -Credential $credential -Computer localhost
This looked like it might have worked, but when reviewing the install log I see the following error;
2015-03-09 13:15:19Z Property DbSqlServer set to: 'SQLSERVER001'
2015-03-09 13:15:23Z Login failed for user 'NT AUTHORITY\ANONYMOUS LOGON'.
My original DeployAOS.ps1 script contains this line, which kicks off the install;
# Perform AOS Installation
Start-Process -FilePath $exeAOSSetup -ArgumentList $cfgAOS -Wait
I have also tried just modifying my DeployAOS.ps1 to set the 'System.Management.Automation.PSCredential' object w\ Username\Password, and doing something like this;
# Perform AOS Installation
Start-Process -FilePath $exeAOSSetup -ArgumentList $cfgAOS -Credential $credentials -Wait
And it really didn't like that. It feels like the AOS setup needs to be executed under a domain user, that has access to the SQL server, and maybe even have a user profile loaded while setup runs (So it can create a desktop shortcut, etc.)
Any ideas how I might go about solving this problem? I'm fairly new to scripting in powershell, so any help would be appreciated.

How to run w32tm in non-elevated powershell

I am writing a helper script that will go through a list of servers and verify they are in sync with the NTP. The script shall be run as a normal script on request by the facility operator and shall request for Admin credentials if the target is not in sync. We unfortunately cannot change the NTP configuration at the moment so we have to make workaround.
What I have right now (and it works beautifully if the script is run as administrator) is a command ("w32tm /query /status" of a remote computer) that is executed via "Invoke-Command" so I can pass it Admin credentials.
My idea was to avoid using WinRM since the hostname resolution is not working properly in our system (it requires some painful host-to-IP-and-back-to-proper-hostname resolution) which makes the WinRM useless.
The command w32tm can obtain status of a remote computer but it needs to be run as administrator for it.
In both cases (run as administrator and run as normal user and later providing the credentials) the $script is executed as domain\administrator (confirmed with the check of Admin role and the "WhoAmI" command) but the status is only obtained when the whole script is executed as administrator.
For the execution as normal user I receive the error:
The following error occurred: Access is denied. (0x80070005)
All machines I use obviously allow remote execution since it works with administrator user.
So basically my question is why is the "w32tm ..." command not allowed in the $script if the role of the user is appropriate (it is administrator role) for the task?
The part of the script which I can't resolve:
function synchronize_remote_machine ($target, $username, $cred)
{
$script = {
param( [String] $compName )
$user = [Security.Principal.WindowsIdentity]::GetCurrent();
$userIsAdmin = (New-Object Security.Principal.WindowsPrincipal $user).`
IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
if (-not $userIsAdmin)
{
Write-Warning "You do not have Administrator rights to run this script!`n
Please re-run this script as an Administrator!"
}
else
{
whoAmI
w32tm /query /status /computer:$compName
#w32tm /resync /rediscover /computer:$compName
}
}
# resync via WinRM
try
{
#execute_resync_command $target $username $cred
if ($username -eq 'Administrator')
{
# when run as admin
invoke-command -ScriptBlock $script -ArgumentList $target;
}
else
{
# for normal user the initalized credential cals is used
invoke-command -computername "localhost" -Credential $cred -ScriptBlock $script -ArgumentList $target
}
}
catch
{
Write-Error "Error executing resync command on host $target."# -foregroundcolor "red"
throw
}
}
Rather than (re-)running the script with elevated privileges, I'd grant the operators group the SeSystemtimePrivilege on those servers. You can do that either with a group policy or by running ntrights.exe from the Server 2003 Resource Kit Tools on each server:
ntrights +r SeSystemtimePrivilege -u DOMAIN\operators
Even if you execute it as administrator, do to try to run you script in an elevated process ?
You can acheive that using Start-Process CmdLet.
start-process 'c:\system32\WindowsPowerShell\v1.0\powershell.exe' -verb runas -argumentlist "-file YourScript.ps1"

Automated Uninstall Service via MSI Not Working

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.