Clean session on user interupt - powershell

Is there a way to catch when the user interrupt the powershell script.
I need to exit a PSSession.
I have test using a try finally but the finally is not executed when the script is interupted Ctrl+c
try
{
$s = New-PSSession -ComputerName MYCOMPUTER -Credential (Get-Credential -Credential admin )
Invoke-Command -ScriptBlock { Get-EventLog -LogName Application } -Session $s
}
catch [Exception]
{
}
finally
{
echo "Ending the session"
Remove-PSSession $s
}

As far as I know it does not exist a script that is run when PowerShell ends. But you can have a look to System.Management.Automation.PsEngineEvent class which provides the events you can subscribe to with Register-EngineEvent CmdLet.
So run a PowerShell command line and execute :
Register-EngineEvent -SourceIdentifier ([System.Management.Automation.PsEngineEvent]::Exiting) -Action {[console]::Beep()}
Then close the PowerShell Command line (exit or click on the upper right corner) and you will ear something. Be carefull if the PowerShell process is killed (using task manager for example) the event is not fired.

Related

How to forward Powershell.Exiting event when remoting on Linux

I have a script on linux that connects to a remote. When exiting, I have to exit twice (once from the remote, and once from the local).
I would like to connect these two, so that I have to exit only once.
Here is what I hoped would work:
pwsh -NoExit -Command '
Register-EngineEvent -SourceIdentifier PowerShell.Exiting -Action {
Write-Host "Received Exiting"
}
$creds = Get-Credential -UserName sysadm;
$pss = New-PSSession -ComputerName remote-pc -Authentication Negotiate -Credential $creds;
Invoke-Command -Session $pss {
Register-EngineEvent -SourceIdentifier PowerShell.Exiting -Forward;
}
Enter-PSSession $pss
'
Based on the Register-EngineEvent docs I expected to see the message - and manually creating the event worked:
...
[remote-pc] PS C:\Users\sysadm\Documents> $null = New-Event PowerShell.Exiting
Received Exiting
[remote-pc] PS C:\Users\sysadm\Documents> exit
# Expecting a message here...
PS /home/local-user > $null = New-Event PowerShell.Exiting
Received Exiting
PS /home/local-user > exit
Received Exiting
I hope I was able to describe my problem.
Why isn't exiting the session sending the PowerShell.Exiting event?
If not via PowerShell.Exiting, is it possible to achive the "double-exit" in an other way?
If it weren't for a bug in Enter-PSSession with respect to in-script and CLI use, still present as of PowerShell (Core) 7.2.1, your problem wouldn't need solving, because simply omitting -NoExit would do what you want, with no need for event handling:
# !! SHOULD work, but doesn't as of PowerShell (Core) 7.2.1
pwsh -Command '
$creds = Get-Credential -UserName sysadm;
$pss = New-PSSession -ComputerName remote-pc -Authentication Negotiate -Credential $creds;
Enter-PSSession $pss
'
That is, if it weren't for the bug, Enter-PSSession would synchronously enter an interactive remote session that wouldn't close until the user interactively terminates it, at which point the entire pwsh process would automatically terminate and return control to the caller.
The bug is detailed in this answer and has been reported in GitHub issue #16350
Unfortunately, there is no good workaround that I'm aware of:
Your use of -NoExit bypasses half of the bug and enables a remote interactive session to be entered.
However, this has the side effect of keeping the enclosing local session alive too, necessitating the second exit command you're trying to avoid.
As you've observed, your attempt to avoid that via an engine-exit event forwarded from the remote session isn't effective; from what I can tell, the reason is that the remote session doesn't actually end at the point it is exited with exit (or the equivalent Exit-PSSession), but ends only when the enclosing local session is exited - and the latter invariably requires submitting exit interactively.
For your use case, to ease the pain somewhat, you could put the remote session setup and entering commands in a *.ps1 script or a function placed in your $PROFILE file instead, and call that manually, after first entering an interactive (local) pwsh session.

Power shell Invoke remote script is not working

I am trying to invoke a remote bat file from my local machine that should start and run the bat in a remote machine terminal. My script is able to connect and show me the output. However, it is invoking the remote bat file but waiting on my screen with bat file output. and my idea is the bat file should invoke and running in the remote machine rather than showing the output on my terminal local terminal. What should be the way here?
loop{
Invoke-Command -ComputerName $computer -credential $cred -ErrorAction Stop -ScriptBlock { Invoke-Expression -Command:"cmd.exe /c 'C:\apacheserver.bat'" }
}
From what I understand you want the .bat file to not show you the result. If so, you should do it with the Out-Null or by redirecting STDOUT and STDERR to NULL.
IE: Invoke-Expression -Command:"cmd.exe /c 'C:\apacheserver.bat'" | Out-Null
If I'm understanding correctly, you want to suppress the output from the .bat file inside your local console? If so, redirecting to some form of $null is the way to go. You also shouldn't need Invoke-Expression as Invoke-Command can run the file directly:
Invoke-Command -ComputerName $computer -Credential $cred -ErrorAction Stop -Scriptblock {
cmd.exe /c 'C:\apacheserver.bat' | Out-Null
}
You could also use > $null instead of Out-Null if you prefer.
If you want to redirect the output of the Invoke-Command call to the remote console instead, that kind of defeats the purpose of the cmdlet. You could try redirecting the console output to a file on the remote computer if you want a local record:
$remoteFile = '\\server\C$\Path\To\Output.txt'
Invoke-Command -ComputerName $computer -Credential $cred -ErrorAction Stop -Scriptblock {
cmd.exe /c 'C:\apacheserver.bat' > $remoteFile
}
If I understand you, you want to run the script in the background and not wait for it:
Invoke-Command $computer -Credential $cred { start-process C:\apacheserver.bat }

Wait for remote Powershell script to finish

I'm creating a powershell script to import Dynamics NAV Application Objects to my Dynamics NAV 2018 database.
Microsoft is providing a PS cmdlet - Import-NAVApplicationObjects - to do so, but I have trouble to wait for the command to finish.
I have a calling script which does the following
$PSSession = New-PSSession -ComputerName $TargetMachine -Credential $MyCredential
Invoke-Command -Session $PSSession -ArgumentList $Database_Name_user, $MyCredential -ScriptBlock {
$process = Start-Process powershell.exe -Verb RunAs -Wait -PassThru -ArgumentList "-File `"C:\Users\User\Desktop\Database\Import1.ps1`" $args[0]"
$process.WaitForExit()
if ($process.ExitCode -ne 0) {
throw $process.StandardError.ReadToEnd()
}
}
The script Import1.ps1 on my $TargetMachine looks like this
param(
[String]$Database_Name_user
)
try {
$AllFiles = "C:\Users\User\Documents\AllFiles.txt"
$Modules = "C:\GIT\Loading Components\NAVModuleImport.ps1"
$OutputBuffer = import-module $Modules
Import-NAVApplicationObject -Path $AllFiles -DatabaseServer "SQLSRV001" -DatabaseName $Database_Name_user -SynchronizeSchemaChanges "No" -ImportAction "Overwrite" -Confirm:$false | Out-Null
}
catch{
$_ | Out-File "C:\Users\User\Documents\Import1.txt"
}
The file AllFiles.txt has a size of 220 MB and contains more than 7700 Dynamics NAV Application Objects (tables, pages, codeunits and so on).
When I launch the script which executes Import-NAVApplicationObject directly from the remote computer stored in $TargetMachine everything works smootly and the process takes up to 10 to 15 minutes, as expected.
When calling the script as shown in the first code example the output stops for a minute and then says everything is done.
Any help is appreciated, thank you in advance.
Edit: Solution
I noticed that my scripts as shown are working, the Import-NAVApplicationObjects cmdlet just failed.
When I elevate the powershell process on the remote computer and run the import, the cmdlet tried to authenticate as NT-Authority\Anonymous to the database.
Then I passed the credentials of the user that opens the remote PSSession to Import1.ps1 and used the parameters -UserName and -Password of the import cmdlet.
Sadly this process failed again.
Now I tried some things with the user I want to use for authenticating, changing passwords etc and it worked! The password contained a semicolon ; and apparently the import cmdlet was not happy with that.
So my simple solution is: Removing the semicolon from the password.

PowerShell Remote script exiting, but not exiting parent script

Below is a script we are using in TeamCity. We are sporadically getting a hung build during this step and some others similarly written. From what I've been able to find out is that the error is occurring between the two servers.
The script creates a remote session and executes on the remote machine. The remote script completes successfully, but never causes this script to exit which leads to a build step hanging.
$username = '%SvcAcct%'
$password = '%SvcAcctPwd%'
$credentials = New-Object System.Management.Automation.PSCredential `
-ArgumentList #($username,(ConvertTo-SecureString -String $password -AsPlainText -Force))
$remoteSession = New-PSSession -ComputerName %RemoteServer% -Authentication Credssp -Credential $credentials
Invoke-Command -FilePath %teamcity.build.checkoutDir%\NovoDeploy\Powershell\InstallNovo.ps1 `
-ArgumentList "C:\NovoDeploy\Installers","C:\NovoDeploy\%InstallSettingsConfigFileName%","%SvcAcctPwd%" -Session $remoteSession
#Get the exit code from the remote session so
#we can pass it through to TeamCity and fail the build.
$remoteSessionExitCode = Invoke-Command { $lastexitcode } -Session $remoteSession
exit $remoteSessionExitCode
Try using remove-pssession -$remoteSession at the end of your script.
Remove-PSSessioncloses one or more Windows PowerShell sessions (PSSessions). I'm thinking maybe your exit command is just exiting the remote powershell session you use to run the remote command and then continuing as a local powershell session after your exit. If you add remove-pssession after you assign the exitcode variable, then maybe your exitcommand will exit the local terminal as needed.
Just a theory but it's worth testing out.

PowerShell start-sleep cmdlet

Hey I am very new to PowerShell and found one of Ed Wilson's helpful scripts on his blog: http://blogs.technet.com/b/heyscriptingguy/archive/2012/11/12/force-a-domain-wide-update-of-group-policy-with-powershell.aspx.
I needed to customize it a little for my needs and just need some help getting the right code down.
I will just use his code because all I did was replace it with my credentials and AD info:
$cn = Get-ADComputer -filt *
$cred = Get-Credential iammred\administrator
$session = New-PSSession -cn $cn.name -cred $cred
icm -Session $session -ScriptBlock {gpupdate /force}
What I added was the next two lines to attempt to pause the script to allow the gpupdate to process then restart the computer(s):
Start-Sleep -s 120
Restart-Computer -ComputerName $cn.name
When I run the script all together it seems to just hang after I enter my credentials. My guess would be it doesn't like the way I present the Start-Sleep cmdlet because I can run the Restart-Computer cmdlet with success without Start-Sleep. The problem is it doesn't wait for gpupdate to finish so the policy doesn't get pushed. I do not get any errors when I run the script, it just hangs. I have left it running for about 10 minutes with no success.
I appreciate any help or suggestions to make this script work properly. Thanks in advance for any input.
There's nothing wrong with your sleep invocation but it isn't the best way to go. You can wait on the job to finish with a timeout (in case the command hangs) e.g.:
$job = icm -Session $session -ScriptBlock {gpupdate /force} -AsJob
Wait-Job $job -Timeout 120
Remove-PSSession $session
Restart-Computer $cn.name
#Keith Hill is right, sleep looks good. Another thing you can do is push the sleep/restart commands onto your target machines:
icm -Session $session -ScriptBlock {
gpupdate /force; Start-Sleep -s 120; Restart-Computer -Force}