How to forward Powershell.Exiting event when remoting on Linux - powershell

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.

Related

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.

Using PowerShell to execute a remote script that calls a batch file

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"}

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.

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.

Invoke-Command and Start-Process Issues

I'm trying to execute the following script:
$Cred = Get-Credential
Invoke-Command -Computername Localhost -Cred $Cred -Scriptblock {Start "Notepad.exe" -Wait}
Well, the notepad comes up no problem as Administrator but it is not visible in the current user's account.
I think it's not possible to see gui in an interactive session with different credential, it live in another user session.
Workaround:
start-process notepad.exe -Credential $Cred
I've run into this problem with PS Remoting and could not find a way to get an app running under one set of credentials to show up on the interactive desktop of a different user. I eventually gave up and used SysInternals utility psexec along with its -i parameter.