How to pause a powershell script until a debugger attaches - powershell

I have an application which allows you to run a user provided PowerShell script. I want inject some code at the top of the script to pause the running script on the first line, and wait for a debugger (ie, PowerShell ISE) to attach.
A good example of what I want to achieve is how DSC pauses and waits for you to attach:
PS C:\> Enable-DscDebug -BreakAll
PS C:\> Start-DscConfiguration .\temp\DSCTestClass -wait -force
WARNING: [DEV-14257-44]: [DSCEngine] Warning LCM is in Debug 'ResourceScriptBreakAll' mode. Resource script processing will be stopped to wait for PowerShe1l script debugger to attach.
WARNING: [DEV-14257-44]: [[FileResource]file] Resource is waiting for PowerShell script debugger to attach. Use the following commands to begin debugging this resource script:
Enter-PSSession -ComputerName DEV-14257-44 -Credential <credentials>
Enter-PSHostProcess -Id 596 -AppDomainName DscPsPluginWkr_AppDomain
Debug-Runspace -Id 4
(this example from https://blogs.msdn.microsoft.com/powershell/2016/03/14/debugging-powershell-dsc-class-resources/)
Unfortunately, when I try and setup a similar kind of wait, powershell.exe automatically launches the command line debugger:
Given the file test.ps1:
set-psbreakpoint -script "test.ps1" -line 4
write-host "pid is $pid"
write-host "Pausing for debugger to attach"
write-host "Paused waiting for debugger to attach"
When I run it (even in -noninteractive mode), it launches the command line debugger:
PS C:\temp\PowershellDebugging> powershell -file .\test.ps1 -noninteractive
ID Script Line Command Variable Action
-- ------ ---- ------- -------- ------
0 test.ps1 4
pid is 13228
Pausing for debugger to attach
Entering debug mode. Use h or ? for help.
Hit Line breakpoint on 'C:\temp\PowershellDebugging\test.ps1:6'
At C:\temp\PowershellDebugging\test.ps1:6 char:1
+ write-host "Paused waiting for debugger to attach"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[DBG]: PS C:\temp\PowershellDebugging>
How can I get PowerShell to break/pause execution without launching the command line debugger?
EDIT:
The script that is being executed only exists for a short time (it is written to disk, executed, then cleaned up). Also, the powershell.exe instance that runs it only exists for a short time - there is no long running process to manually attach to and set breakpoints on before execution.

After getting some help from the PowerShell team (thanks!), it turns out the key is using New-RunSpace. Normally, if you just execute a script, it will run in the default RunSpace, and that will have the built-in PowerShell debugger already attached.
The following demonstrates how to use it:
# create a fake script that simulates the user provided scrip
#'
Write-Output "Starting Test"
1..20 | foreach {
Write-Output "I Love PowerShell!"
}
Write-Output "Test Complete"
'# | Set-Content -Path c:\temp\Test.ps1
# create a launcher script
#'
Write-Output "Process Id: $pid"
$ps = [powershell]::Create([System.Management.Automation.RunspaceMode]::NewRunspace)
Write-Output "Runspace Id: $($ps.Runspace.Id)"
$ps.AddScript(‘Wait-Debugger; C:\temp\Test.ps1’).Invoke()
'# | Set-Content -Path c:\temp\LaunchTestInNewRunspace.ps1
# run the launch script
PS C:\> powershell -File c:\temp\LaunchTestInNewRunspace.ps1
Process Id: 14288
Runspace Id: 2
In another window:
# in another window
PS C:\> Enter-PSHostProcess -Id 14288
[Process:14288]: PS C:\> Debug-RunSpace -Id 2
Debugging Runspace: Runspace2
To end the debugging session type the 'Detach' command at the debugger
prompt, or type 'Ctrl+C' otherwise.
Entering debug mode. Use h or ? for help.
At line:1 char:16
+ Wait-Debugger; C:\temp\Test.ps1
+ ~~~~~~~~~~~~~~~~
This breaks into the debugger, ready to step into Test.ps1.

Related

How to prevent exit of host and return exit code?

Ansgar Wiechers' answer works well whenever starting a new PowerShell process. https://stackoverflow.com/a/50202663/447901 This works in both cmd.exe and powershell.exe.
C:>type .\exit1.ps1
function ExitWithCode($exitcode) {
$host.SetShouldExit($exitcode)
exit $exitcode
}
ExitWithCode 23
In a cmd.exe interactive shell.
C:>powershell -NoProfile -Command .\exit1.ps1
C:>echo %ERRORLEVEL%
23
C:>powershell -NoProfile -File .\exit1.ps1
C:>echo %ERRORLEVEL%
23
In a PowerShell interactive shell.
PS C:>powershell -NoProfile -Command .\exit1.ps1
PS C:>$LASTEXITCODE
23
PS C:>powershell -NoProfile -File .\exit1.ps1
PS C:>$LASTEXITCODE
23
HOWEVER... Running the .ps1 script inside an existing interactive PowerShell host will exit the host completely.
PS C:>.\exit1.ps1
<<<poof! gone! outahere!>>>
How can I prevent it from exiting the host shell?
Do not use $host.SetShouldExit(): it is not meant to be called by user code.
Instead, it is used internally by PowerShell in response to an exit statement in user code.
Simply use exit 23 directly in your exit1.ps1 script, which will do what you want:
When run inside a PowerShell session, the script will set exit code 23 without exiting the PowerShell process as a whole; use $LASTEXITCODE to query it afterwards.
.\exit.ps1; $LASTEXITCODE # -> 23
When run via the PowerShell CLI:
with -File, the exit code set by the script automatically becomes the PowerShell process' exit code, which the caller can examine; when called from cmd.exe, %ERRORLEVEL% reflects that exit code.
powershell -File .\exit.ps1
:: This outputs 23
echo %ERRORLEVEL%
with -Command, additional work is needed, because PowerShell then simply maps any nonzero exit code to 1, which causes the specific exit code to be lost; to compensate for that, simply execute exit $LASTEXITCODE as the last statement:
powershell -Command '.\exit.ps1; exit $LASTEXITCODE'
:: This outputs 23
echo %ERRORLEVEL%
For more information about how PowerShell sets exit codes, see this answer.
If:
you do not control how your script is invoked via the CLI, yet must ensure that the correct exit code is reported even when the script is invoked via -Command,
and you're willing to assume the risk of using $host.SetShouldExit(), even though it isn't designed for direct use,
you can try the following:
function ExitWithCode($exitcode) {
if ([Environment]::CommandLine -match ( # Called via the CLI? (-File or -Command)
' .*?\b' +
[regex]::Escape([IO.Path]::GetFileNameWithoutExtension($PSCommandPath)) +
'(?:\.ps1\b| |$)')
) {
# CAVEAT: While this sets the exit code as desired even with -Command,
# the process terminates instantly.
$host.SetShouldExit($exitcode)
}
else {
# Exit normally, which in interactive session exits the script only.
exit $exitcode
}
}
ExitWithCode 23
The function looks for the file name of the executing script on the process command line to detect whether the enclosing script is being invoked directly via the CLI, via the automatic $PSCommandPath variable, which contains the script's full path.
If so, the $host.SetShouldExit() call is applied to ensure that the exit code is set as intended even in the case of invocation via -Command.
Note that this amounts to a repurposing of the effectively internal .SetShouldExit() method.
Surprisingly, this repurposing works even if additional commands come after the script call inside the -Command string, but note that this invariably means that the success status of the truly last command - if it isn't the script call - is then effectively ignored.
This approach isn't foolproof[1],but probably works well enough in practice.
[1]
There could be false positives, given that only the file name is looked for, without extension (because -Command allows omitting the .ps1 extension of scripts being called).
There could be false negatives, if the script is being called via another script or via an alias.
How can I prevent it from exiting the host shell?
You can check if the currently running PowerShell process is a child of another PowerShell parent process, and only call $host.SetShouldExit() when that condition is true. For example:
function ExitWithCode($exitcode) {
# Only exit this host process if it's a child of another PowerShell parent process...
$parentPID = (Get-CimInstance -ClassName Win32_Process -Filter "ProcessId=$PID" | Select-Object -Property ParentProcessId).ParentProcessId
$parentProcName = (Get-CimInstance -ClassName Win32_Process -Filter "ProcessId=$parentPID" | Select-Object -Property Name).Name
if ('powershell.exe' -eq $parentProcName) { $host.SetShouldExit($exitcode) }
exit $exitcode
}
ExitWithCode 23
Hope this helps.

Write realtime powershell output during TFS release execution

In our company we use TFS 2017 (update 1) for building and releasing our products. The release part is made up of several steps which include the execution of some Powershell scripts.
This is how I configure the PS step.
What I noticed is that the output of the powershell scripts is not written realtime while it is executing, but all together in the end of the PS task. This is very annoying in case of long running scripts as we are not able to see the live progress of the task, but we have to wait the task to finish to see the results.
I wrote some simple PS scripts to debug this problem but neither using write-host (this does not write nothing at all, even in the end of the task) nor using write-output nor with write-verbose -verbose allows me to write realtime output.
This is one example script I tried, without success.
Write-Output "Begin a lengthy process..."
$i = 0
while ($i -le 100)
{
Start-Sleep 1
Write-Output "Inner code executed"
$i += 10
}
Write-Output "Completed."
Did you ever found yourself in this situation?
Regards
I can reproduce this issue, based on my test realtime output is not supported for the PowerShell on Target Machines task.
Write-output or write-verbose -verbose just can output to console but it's not real-timed, the output only displays once the powershell script completely executed.
To display the real-time output you can use the Utility:PowerShell task instead of the Deploy:PowerShell on Target Machines task.
So, as a workaround you can deploy an agent on the target machine which you want to run the powershell script, then trigger the release using that agent running powershell script with Utility:PowerShell task.
UPDATE:
Well, find another workaround with Utility:PowerShell task:
1.Set up WinRM for target computers, refer to WinRM configuration
2.Copy the target PS script to the target machine (D:\TestShare\PStest.ps1 in below sample)
3.Create a PowerShell script to call the Powershell.exe to run the target powershell script on target machine, see below sample:
Param(
[string]$computerName = "ICTFS2015.test.com",
)
$Username = "domain\usename"
$Password = ConvertTo-SecureString "Possword" -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential($Username,$password)
Invoke-Command -ComputerName $computerName -Credential $cred -ScriptBlock {Invoke-Expression -Command:"powershell.exe /c 'D:\TestShare\PStest.ps1'"}
4.Add a Utility:PowerShell task to run above PowerShell script. (You can check in or run Inline Script).

Another Task Scheduler not executing PowerShell

I have a simple PowerShell script that just creates a file. Testing this for a bigger PowerShell script.
Running on Windows Server 2012 R2 - 64-bit
its running under the Administrator account.
Run whether user is logged on or not
Run with highest privileges
Action:
Program/Script
:
Powershell.exe (I've tried it this way and the full path)
Add argumetns: -NoProfile -executionpolicy remotesigned -file C:\Scripts\test.ps1
Get-executionPolicy: RemoteSigned
added "Administrator" to:
Set-PSSSessionConfiguration -Name Microsoft.PowerShell -ShowSecurityDescriptorUI
"Administrator" has "Log on as batch job" permissions
"Administrator" is in the Admins group
PowerShell Script for testing:
$text = "Hello World"
$text | Set-Content TestMyFile.txt
$text | Out-File TestMyFile.txt
$text > TestMyFile.txt
# This is to write into a file or append to the text file created:
$text | Add-Content TestMyFile.txt
$text | Out-File TestMyFile.txt -Append
$text >> TestMyFile.txt
Nothing fancy, just trying to make sure Task Scheduler will execute a PowerShell script.
So What am I missing?
Use full paths in your output code, and for any future problems with running scripts from Task Scheduler, you should first put in logging and try/catch blocks to see what errors/outputs the script is generating before seeking further assistance. Doing this will help you find the answer faster & learn faster at the same time.
Tip: Always only name the program in the 'Program to run' aspect of Task Scheduler, as the Task Scheduler uses its own wrapper to execute the action, and in rare occasions, it can produce undesired results if you place the entire execution line in this field. Always put parameters in the parameters field.

Source a PowerShell script on each new shell

I have a script called Restart-Audio.ps1
Stop-Process -Confirm -Name musicapp*
Stop-Service audiosrv
Stop-Service AudioEndpointBuilder
Start-Service audiosrv
Invoke-Item C:\path\to\music\app.exe
Every time I start a new PowerShell session, I want this script to run when I type "Restart-Audio" like a normal cmdlet. When I tried to add this to my PowerShell profile, it tried to run the script, asking for the confirmation to stop the processes. I don't want it to run as soon as a PowerShell session starts; I want the command to run when I tell it to.
Thanks.
This can be done using Powershell profiles, see here for details.
You basically create (if the file doesn't already exist) C:\Users\<<UserName>>\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1 and that can contain your above script.
I'd construct the file as:
Function Restart-Audio{
Stop-Process -Confirm -Name musicapp*
Stop-Service audiosrv
Stop-Service AudioEndpointBuilder
Start-Service audiosrv
Invoke-Item C:\path\to\music\app.exe
}
Then you just open Powershell and type Restart-Audio to run that function.
Additionally, if you save the script to any folder in your $env:PATH you can just type the first few characters of the script name and TAB complete to run the script.

Executing Powershell from Command Line

I've spent the last week developing a Powershell script. It now works ok when I start it from a Powershell window. However, I need the script to be called from TFS build. I've tried using the syntax Powershell & 'script' from a TFS Build InvokeProcess control but nothing seems to happen. So, I've gone back to basics and created the following script:
Write-Host " Write-Host line"
Write-eventlog -logname "Application" -source "BizTalkDeployment" -eventId "01" -entrytype "Information" -message "test"
I've saved the script as c:\temp\ps_test.ps1. I've opened a command window as admin and tried the following:
powershell & 'c:\temp\ps_test.ps1"
This puts the command line into powershell mode and I get:
PS c:\>
But nothing else happens.
At the end of my TFS build I have an InvokeProcess control that uses "Powershell" for its FileName property and then the following arguments:
String.Format(" ""& '{0}' '{1}' '{2}' '{3}' '{4}' '{5}' '{6}' '{7}' '{8}' '{9}' "" ", DeploymentScriptFileName, IO.Path.GetDirectoryName(DeploymentScriptFileName), "ExecuteBizTalkAppMSI.ps1", "bin\debug\x.Int.MIS-3.0.0.msi", "x.Int.MIS.Deployment.btdfproj", TargetServerPath, "d-vasbiz01", "c:\biztalkdeployment", "C:\Program Files (x86)\x.Int.MIS for BizTalk 2010\3.0", "BTSSvc*MIS*")
The build script runs ok but the InvokeProcess control seems to do nothing - just like my experience with the command line.
Can anyone see where I'm going wrong please?
Either
powershell -file C:\temp\ps_test.ps1
or
powershell "&{ <# code here #> }"