How do I troubleshoot Powershell user data scripts on AWS EC2? - powershell

I am trying to run a powershell script from the user data box when creating an ec2 instance from a custom AMI. I have enabled user data execution on the config before creating the ami.
This is what i put into user data
<powershell>
c:\scripts\github-download.ps1 someuser somepassword
</powershell>
The script it is calling is shown below.
Param($gituser, $gitpass)
C:\Users\Administrator\AppData\Local\GitHub\shell.ps1
git clone https://"$gituser":"$gitpass"#github.com/somegitrepo |out-null
I have no idea why this isn't working. Am i doing something wrong here? Any help really appreciated.

Instead of calling the user data using the <powsershell> tag, call PowerShell itself using the <script> tag. You gain command line control over its invocation, and can control execution policy and other command line settings directly:
<script>
PowerShell -ExecutionPolicy Bypass -NoProfile -File c:\scripts\github-download.ps1 -user USER -password PASSWORD
</script>
In your script, setup the beginning and end sections of your script as below:
# Server script called from userdata in this format
# <script>
# PowerShell -ExecutionPolicy Bypass -NoProfile -File c:\scripts\github-download.ps1 -user USER -password PASSWORD
# </script>
param (
[string]$user = $(throw "-user is required."),
[string]$password = $(throw "-password is required."),
)
Start-Transcript -Path C:\userscriptlog.txt
Import-Module WebAdministration
if ([System.Diagnostics.EventLog]::SourceExists("Userdata") -eq $False) {
New-Eventlog -Logname Application -Source 'Userdata'
}
Write-Eventlog -Logname Application -Source 'Userdata' -EventId 1 -EntryType Information -Message 'Begining post-deployment configuration script'
-- YOUR MAIN SCRIPT HERE --
Write-Eventlog -Logname Application -Source 'Userdata' -EventId 1 -EntryType Information -Message 'Post-deployment configuration script complete'
Stop-Transcript
For error handling in your script, you need to use robust exception handling and logging for each command, again to make troubleshooting and debugging easy. This block simply gets the current instance ID, but note the exception handling and logging built in:
# get instance-id
try {
$InstanceId = (Invoke-WebRequest http://169.254.169.254/latest/meta-data/instance-id).content
} catch {
$_.Exception.message | out-file c:\InstanceId_error.log
Write-Host "FATAL: InstanceId exception"
Exit
}
if (!$InstanceId) {
Write-Host "FATAL: InstanceId is null"
Exit
} else {
$InstanceId | out-file C:\InstanceId.txt
Write-Host "InstanceId: $InstanceId"
}
Try that approach to any command or shell invocation that you need to implement.
This powershell script 'wrapper' for user data scripts allows optional command line parameters, produces a transcript of execution, and logs events to the Windows event log, to confirm basic execution of the script.
It will provide a flexible framework for any Powershell based user data script, allow for easy debugging and testing.

| out-null silences any errors that could be happening with git clone so you won't know what is wrong unless you pipe the error somewhere else or just don't use | out-null.
I would manually run the command on the EC2 instance without the | out-null before you try and use user data to automate anything.

Related

Running PowerShell with admin credentials for users

I have a PowerShell script that restarts the Spooler service, then locates all printers with a specific name, removes those printers, then adds the printers back. I want to be able to go on a user's PC and run the script and input my admin credentials, but when I do, it doesn't find the printers because printers are per user not per PC. Is there a way to run the script as the user with elevated permissions in a single PS instance?
# Check if ps is running as admin otherwise open this script as admin
if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { Start-Process powershell.exe "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs; exit }
# Local printer name to search for
$FindName = "*DSM*"
# Store local printer object for later use
$Printers = Get-Printer -Name $FindName
if(!$Printers){
$NotFound = "No printer with the name "
$NotFound2 = " was found."
$NotFound + $FindName + $NotFound2
}else{
Write-Output "Printer found"
# Restart spooler service
Write-Output "Restarting Spooler"
Restart-Service -Name Spooler -Force
# Wait for Spooler to come back online
Start-Sleep -s 5
# loop through all printers found and re-add each one
foreach($Printer in $Printers){
# Remove printer
Write-Output "`nRemoving " $Printer.Name
Remove-Printer -Name $Printer.Name
# Add printer
Write-Output "Re-adding " $Printer.Name
Add-Printer -ConnectionName $Printer.Name
}
Read-Host -Prompt "Press Enter to exit"
}
Basically, you need to run the service management pieces as an admin user (presumably, end users don't have admin on their workstations) but the printer management must happen from the end user's account. You'll essentially use Start-Process to run the service management piece as your admin account, but let the rest of the script run in the end user's context:
# Restart spooler Service
# Splatting used here for readability
$psPath = "$env:SystemRoot/System32/WindowsPowerShell/v1.0/powershell.exe"
$spArgs = #{
Wait = $True
Credential = Get-Credential DOMAIN.tld\adminuser
FilePath = $psPath
ArgumentList = '-Command "$p = Start-Process -PassThru -Wait -FilePath \"{0}\" -Verb RunAs -ArgumentList \"Restart-Service -Force -EA Stop spooler\"; exit $p.ExitCode"' -f $psPath
PassThru = $True
ErrorAction = 'Stop'
}
$p = Start-Process #spArgs
if( $p.ExitCode -ne 0 ) {
# The service failed to restart. Handle the failure case.
}
The way this works:
Run a new powershell.exe process as your admin user, then making sure to elevate permissions by running powershell.exe a second time with the RunAs Verb. You will need to input the credential each time the script is run.
As indicated in the comments, -Credential and -Verb are mutually exclusive.
There is not really a graceful way to do this, to make this most readable I have use a literal string with the format operator -f to avoid an even worse escape-hell.
The -Command parameter needs to be provided within double-quotes when executed via Start-Process, or else it will only render the literal string and not actually execute the nested command.
The youngest PowerShell process will perform the service restart.
Use the -Wait flag to wait until the process exits for both invocations of Start-Process. Start-Process does not block execution by default regardless of application type.
Make Restart-Service throw a terminating error with -EA Stop if it encounters a problem. This will guarantee a non-zero exit code is returned on failure without requiring additional boilerplate code.
Specifying the full path to powershell.exe is optional but a general good practice. You could also simply use powershell.exe in place of the full path to the binary.
Programs run with Start-Process do not set $LASTEXITCODE. Therefore, we return the Process object with -Passthru, then check that its exit code is 0. Any other exit code indicates failure for this operation.
You can remove your admin check from the script since this no longer needs administrative permissions, and Get-Credential will essentially elevate for the service restart.
Note that if you want to perform this from a user who can elevate but you don't want the PowerShell script itself to run elevated, you can run the script without elevation and use the technique above but omit the -Credential parameter which becomes redundant. It would still work but no reason to authenticate when you don't have to.

Check if powerhshell was started using "run as administrator"

i found an old question on this topic. However, i am not clear.
I have a script that checks, if PS has been run using "run as administrator" and if yes it does the job, otherweise it prompts that the script must be run as administrator.
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
$CheckforAdmin = $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
it gives true or false. I have if-else statement that does the rest.
If($CheckforAdmin -eq 'True'){
$MSG = ""
If(($EventLogCheck -ne $EventLog) -or ($EventLogsourceCheck -ne 'True')){
New-EventLog -LogName $EventLog -Source $EventLogSource -ErrorAction Stop
$MSG = "$env:USERNAME has successfully created a new eventlog named $EventLog with the source $EventLogSource."
Write-EventLog -logname $PiEventLog -source $PiEventLogSource -eventID 1021 -entrytype Information -message $MSG
}
else{
$MSG = "$env:USERNAME tried to create an EventLog named $EventLog with the source $EventLogSource. `nSince the EventLog and the source already exist, this step was aborted."
Write-EventLog -logname $EventLog -source $EventLogSource -eventID 1021 -entrytype Information -message $MSG
}
# Wenn der Parameter Silent auf true gesetzt ist, wird das Skript nach der Erstellung des EventLogs unmittelbar beendet.
if($install -eq $true){
Write-Host $MSG
Read-Host 'Press any key to continue...'
}
exit
}
else{
Write-Host "The Script must be executed as admin"
[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms')
[System.Windows.Forms.MessageBox]::Show('Installation: The script must be run as administrator in order to create the event log', 'Run as admin')
exit
}
It works well, if i am logged in with a normal user. But on my server where i want to run the script, i log in as domain administrator. Even if if run the script just double clicking on it, it runs instead of prompting that the script must be run using "run as administrator".
I red the articles about UAC (User Account control) and as far as i understood: running a script using "run as administor" is actually the same as logging in as domain administrator and double clicking on the script.
Is there any other way to check, if the script was run using "run as administrator" option that shows up if u right click on powershell (doesn't matter, whether you are logged in as admin or not) ?
At the top of your script add the line:
#Requires -RunAsAdministrator
then remove all your code to check for an administrator.
If the user running the script is not an elevated administrator, a message will be displayed and the execution of the scripts stops.
Original comment:
How did you implement the prompt? Because obviously, this part only returns $true or $false. I suppose a way to work around this would be something like
PowerShell -NoProfile -ExecutionPolicy Unrestricted -Command "& {Start-Process PowerShell -ArgumentList '-NoProfile -ExecutionPolicy Unrestricted -File ""PS_Script_Path&Name.ps1""' -Verb RunAs}"
To call the script as admin again, if it wasnt before.
Newly added:
Additionally, you have an error in your if-statement. To compare to boolean, you want to have your if-statement like following:
If($CheckforAdmin -eq $true){
Comparing against strings can lead to problems. Otherwise I cannot locate any other errors.

PowerShell Passing Local Parameters to a Remote Script via Invoke-Command

Using PowerShell 4.0. I'm trying to run a script remotely that resides on the remote server. I need to pass a variable from my local script to the remote script. I've gone over dozens of posts and tried at least 10 variations but none seem to work. My initial command was:
Invoke-Command -ComputerName $ServerList -FilePath $piDeployScript -ArgumentList $SDFEnvironment
Where $piDeployScript is the full UNC path to the script on the remote server and $SDFEnvironment is a simple string parameter passed into my script. It looks like the parameter is not getting passed to the script. Is there a simple way to do this? How far off am I?
Update: I don't write the remote scripts, they're written by other groups in the company. The only thing I know is what field(s) need to be passed and they're all positional. I don't know the actual parameter names in the remote scripts. I could get them but I'm trying to write something that will work regardless of the parameters on the remote script. I get passed a list of parameter values (in this case 1 parameter) and send that list of strings to the remote script.
Update: Per #dugas this is the test script I'm running on my own server to test the functionality
Param
(
# SDF Environment
[Parameter(Position=0)]
[string]$SDFEnvironment
)
if ($PSBoundParameters.ContainsKey('SDFEnvironment'))
{
"Script Ran Env $SDFEnvironment"|Out-File -FilePath .\$SDFEnvironment"_LogFile.txt" -Encoding ascii -Force
}
else
{
"Environment Parameter missing"|Out-File -FilePath .\"ERR_LogFile.txt" -Encoding ascii -Force
}
Update: Below is the entire section of my local script that runs the Invoke-Command:
# Run the specifed script on all computers
Write-Output ("Running command ""{0}"" on server(s) ""{1}"" for Environment ""{2}"" " -f $piDeployScript,$SDFServerList,$SDFEnvironment)
try
{
if (Test-Connection -ComputerName $SDFServerList -Quiet)
{
Invoke-Command -ComputerName $SDFServerList -FilePath $piDeployScript -ArgumentList $SDFEnvironment
}
else
{
Write-Error "Remoting not enabled on one or more of ""$SDFServerList"" "
exit 1
}
}
catch
{
Write-Error ("Error running script {0} on server(s) {1}. Check log for more detail." -f $piServerScript,$SDFServerList)
exit 1
}
and here's the output from that code:
Running command "D:\AHP\pi_exceed_presentation\pi_exceed_presentation_deploy.ps1" on server(s) "ad1hfdahp802" for Environment "INT"
D:\a5\RWS_pi_exceed_presentation\Content_Deploy\trunk\deploy\Deploy-Content.ps1
: Error running script pi_exceed_presentation_deploy.ps1 on server(s)ad1hfdahp802. Check log for more detail.
My bad on the $piDeployScript variable. It's not the UNC to the script, it's the local file system address on the remote server. Is that the problem? Is it looking for the script on my local D: drive? Did I just answer my question?
Just to test my questions above I ran this code in PowerShell from my desktop:
$s="ad1hfdahp802"
$SDFEnvironment="INT"
$fp="\\ad1hfdahp802\D$\AHP\pi_exceed_presentation\pi_exceed_presentation_deploy.ps1"
try
{
Invoke-Command -ComputerName $s -FilePath $fp -ArgumentList $SDFEnvironment
}
catch
{
write-output "error running $fp on $s for $SDFEnvironment"
}
The code returned with no error, but the remote script didn't run. I logged into the remote server via RDC and ran the remote script locally in PowerShell with and without a parameter and it worked as expected. Now I'm really stumped.
Local script:
$server='ad1hfdahp802'
$remotepath='\\ad1hfdahp802\d$\AHP\pi_exceed_presentation\pi_exceed_presentation_deploy.ps1'
$scriptPath='d:\AHP\pi_exceed_presentation\'
$SDFEnvironment='INT'
Invoke-Command -ComputerName $server -FilePath $remotepath -ArgumentList($SDFEnvironment,$scriptPath)
Remote Script:
Param
(
# SDF Environment
[Parameter(Position=0)]
[string]$SDFEnvironment,
[Parameter(Position=1)]
[string]$scriptPath
)
Write-Output "Running Script - $SDFEnvironment"
Write-Output "Path: $scriptPath"
$outpath=("{0}\{1}_LogFile.txt" -f $scriptPath,$SDFEnvironment)
"Script Ran Env $SDFEnvironment"|Out-File -FilePath $outpath -Encoding ascii -Force
Write-Output "Script Complete"
This combination works. Originally the remote script used $PSCommandPath (also tried $PSScriptRoot) to determine the output path but that doesn't work on scripts run remotely. The actual path has to be passed in if you want deterministic placement of output files.

How can prevent a PowerShell window from closing so I can see the error?

I'm creating a local PowerShell module downloader script. The module and the script are held on a network share. The script is invoke using:
& '\\net\DSFShare\Shared Data\Powershell Modules\Install-MyModuleManager.ps1'
It copies the script to the standard profile modules folder and then runs Install.ps1 from the module folder. Install.ps1 elevates itself, if needed. Just before the elevated window closes, a red error pops up, but the window closes too quickly for me to see the error. How can I find out what the error is?
The downloader script invokes the installer using:
$installerPath = [IO.Path]::Combine($LocalModulePath, 'Install.ps1')
Write-Host "Installer path: $installerPath"
if (Test-Path $installerPath) {
Write-Host 'Install.ps1 exists. Running Install.ps1'
& $installerPath
}
Note, if from PowerShell, I populate $installerPath and call it using & $installerPath, I don't see the error.
I've checked the Application, System, Windows PowerShell, and Security event logs. There aren't any errors relating to this.
All the script does is create an event source. If you want to run it, you can use:
Remove-EventLog -Source 'My.Module.Manager'
afterwards, to remove it. Here's the script:
Write-Host "Installing module..."
$eventSource = 'My.Module.Manager'
try {
$sourceExists = [System.Diagnostics.EventLog]::SourceExists($eventSource)
} catch [Security.SecurityException] {
Write-Verbose "Caught 'SecurityException': $_.Exception.Message"
}
if ($sourceExists) {
Write-Host "...installation complete..."
} else {
#region ----- Ensure-ProcessIsElevated -----
if ($Verbose) {
$VerbosePreference = "Continue"
}
if ($Debug) {
$DebugPreference = "Continue"
}
Write-Debug "Command line is ___$($MyInvocation.Line)___"
Write-Verbose "Entering script body"
if ($ScriptPath) {
Set-Location $ScriptPath
Write-Verbose "Working directory: $pwd"
}
If (-Not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
Write-Warning "This script must be run with elevated privileges."
Write-Warning "Restarting as an elevated process."
Write-Warning "You will be prompted for authorization."
Write-Warning "You may click 'No' and re-run manually, if you prefer."
If ((Get-WmiObject Win32_OperatingSystem | select BuildNumber).BuildNumber -ge 6000) {
Write-Verbose "This is a UAC-enabled system. Elevating ..."
$CommandLine = "$($MyInvocation.Line.Replace($MyInvocation.InvocationName, $MyInvocation.MyCommand.Definition)) -ScriptPath $pwd"
Write-Verbose "CommandLine: $CommandLine"
Start-Process -FilePath PowerShell.exe -Verb Runas -ArgumentList "$CommandLine"
} else {
Write-Verbose "The system does not support UAC: an elevated process cannot be started."
Write-Warning "This script requires administrative privileges. Please re-run with administrative account."
}
Break
}
Write-Verbose "The script is now running with elevated privileges."
#endregion ----- Ensure-ProcessIsElevated -----
New-EventLog -LogName Application -Source $eventSource
Write-Host "...installation complete..."
}
I'm using PowerShell 4.0.
You basically have three options to prevent the PowerShell Console window from closing, that I describe in more detail in my blog post.
One-time Fix: Run your script from the PowerShell Console, or launch the PowerShell process using the -NoExit switch. E.g., PowerShell -NoExit "C:\SomeFolder\SomeScript.ps1"
Per-script Fix: Add a prompt for input to the end of your script file. E.g., Read-Host -Prompt "Press Enter to exit"
Global Fix: Change your registry key to always leave the PowerShell Console window open after the script finishes running. Here's the two registry keys that would need to be changed:
● Open With → Windows PowerShell
When you right-click a .ps1 file and choose Open With
Registry Key: HKEY_CLASSES_ROOT\Applications\powershell.exe\shell\open\command
Default Value:
"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "%1"
Desired Value:
"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "& \"%1\""
● Run with PowerShell
When you right-click a .ps1 file and choose Run with PowerShell (shows up depending on which Windows OS and Updates you have installed).
Registry Key: HKEY_CLASSES_ROOT\Microsoft.PowerShellScript.1\Shell\0\Command
Default Value:
"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "-Command" "if((Get-ExecutionPolicy ) -ne 'AllSigned') { Set-ExecutionPolicy -Scope Process Bypass }; & '%1'"
Desired Value:
"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -NoExit "-Command" "if((Get-ExecutionPolicy ) -ne 'AllSigned') { Set-ExecutionPolicy -Scope Process Bypass }; & \"%1\""
You can download a .reg file from my blog to modify the registry keys for you if you don't want to do it manually.
It sounds like you likely want to use option #2. You could even wrap your whole script in a try block, and only prompt for input if an error occurred, like so:
try
{
# Do your script's stuff
}
catch
{
Write-Error $_.Exception.ToString()
Read-Host -Prompt "The above error occurred. Press Enter to exit."
}
This will make the PowerShell window wait until you press the Enter key (not any key):
pause
Also simple and easy:
Start-Sleep 10
The simplest and easiest way is to execute your particular script with -NoExit param.
1.Open run box by pressing:
Win + R
2.Then type into input prompt:
PowerShell -NoExit "C:\folder\script.ps1"
and execute.
I'm adding the Registry script to leave the PowerShell window open after it completes.
I took it from blog.danskingdom.com
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\Applications\powershell.exe\shell\open\command]
#="\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" -NoExit \"& \\\"%1\\\"\""
[HKEY_CLASSES_ROOT\Microsoft.PowerShellScript.1\Shell\0\Command]
#="\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" -NoExit \"-Command\" \"if((Get-ExecutionPolicy ) -ne 'AllSigned') { Set-ExecutionPolicy -Scope Process Bypass }; & \\\"%1\\\"\""

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"