Start-Job including custom cmdlet terminates with strange error - powershell

I developed some custom cmdlets that serve for different importing tasks to a SharePoint system. Currently all those cmdlets are being run in a serial kind in a single PowerShell script. I want to change this so that each cmdlet gets executed in a separate task (job).
The main script starts a new job with Start-Job relating to a separate script that contains the call to the cmdlet. The script starts and executes the cmdlet. I also debugged the code of the cmdlet that gets executed. So far so fine.
But after around 15-20 seconds the job just gets terminated with the following error message:
There is an error processing data from the background process. Error reported:
Cannot process an element with node type "Text". Only Element and EndElement
node types are supported..
+ CategoryInfo : OperationStopped: (localhost:String) [], PSRemotingTransportException
+ FullyQualifiedErrorId : JobFailure
+ PSComputerName : localhost
I can't find any information on how to handle such an error. I just don't know what is the problem here.
Do I have to add further functionalities to my custom cmdlets so they can be handled in a job?
Here are the scripts.
Main:
[object]$credentials = Get-Credential -UserName "domain\user" -Message "Log in"
$job = start-job -FilePath "C:\ImportItems.ps1" -Name ImportItems -ArgumentList $credentials
$job | Wait-Job
ImportItems:
[CmdletBinding()]
Param(
[object]$credentials
)
Import-Module C:\Migration\MigrationShell.dll
Import-Items -Credential $credentials

I found a workaround on uservoice, did the trick for me
https://windowsserver.uservoice.com/forums/301869-powershell/suggestions/14915283-job-cmdlets-fail-with-utf-8-codepage
if (
[Console]::InputEncoding -is [Text.UTF8Encoding] -and
[Console]::InputEncoding.GetPreamble().Length -ne 0
) {
[Console]::InputEncoding = New-Object Text.UTF8Encoding $false
}

Related

How to Install Windows Updates on Remote Computer with PowerShell

I'm trying to install Windows Updates on a Remote Computer with this command:
$InstallSplat = #{
AcceptAll = $true
SendReport = $true
IgnoreReboot = if ($Reboot) { $false } else { $true }
PSWUSettings = #{
SmtpServer = "my mail server"
From = "myfrom <myfrom#myfrom.com>"
To = "myto <myto#myto.com>"
Port = 25
}
}
Invoke-Command -ComputerName $_ -Credential $cred -AsJob -ArgumentList $InstallSplat -ScriptBlock {
param([hashtable]$InstallSplat)
Import-Module PSWindowsUpdate
Install-WindowsUpdate #InstallSplat
$Error | out-file C:\install\installwinupdate.log -Append
}
I pass a credential Object with domain admin privileges in $cred but I still always get this error
Install-WindowsUpdate : Access denied (Ausnahme von HRESULT: 0x80070005 (E_ACCESSDENIED)) In Zeile:4 Zeichen:25
+ Install-WindowsUpdate #InstallSplat
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Get-WindowsUpdate], UnauthorizedAccessException
+ FullyQualifiedErrorId : System.UnauthorizedAccessException,PSWindowsUpdate.GetWindowsUpdate
The Command Install-WindowsUpdate itself does not have a credential parameter I could use. The Command needs to run in an elevated PowerShell, but I use an elevated PowerShell when starting this command on my Computer.
I Also tried creating a New-PSSession with my $cred and run Invoke-Command -Session $session instead of Invoke-Command -ComputerName $_ with the same result.
Does anybody know what's happening here? Why do I get Access denied?
It can't have anything to do with passing the $InstallSplat because the same thing happens if I don't pass any parameter at all and write the parameters and their Values directly at the command instead of splatting.
The Problem was, that you can't Download or Install Updates on a machine from another remote machine. Here's a list what you can or can't do remotely when it comes to Windows Updates
The solution is, to create a scheduled task on each server you want to install updates from a remote script, and start that task.
luckily, when you use the PSWindowsUpdate module, you don't have to do that yourself, you can just use Invoke-WUJob (formerly Invoke-WUInstall) which does the trick for you.
I used it like so ($ServerData.Value contains a list of my Servers) and it works like a charm. It creates a scheduled task on each server, and runs them immediately, if you add the -RunNow Parameter.
invoke-WUJob -ComputerName $ServerData.Value -Script { Import-Module PSWindowsUpdate ; Install-WindowsUpdate -AcceptAll -SendReport -IgnoreReboot -PSWUSettings #{From='xy';Port=25;SmtpServer='xy';To='xy'} | Out-File C:\install\PSWindowsUpdateLog.txt -Append} -Confirm:$false -verbose -RunNow
Note that what you specify as a script block in -Script will be pasted to -Command " <here> " in your scheduled task, so you should work with ' inside -Script.

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.

Access Denied when waiting for a Process started with Credentials

I have a script which runs in a user context of a user that does not have permission to Install Applications. I need to install an exe, so I need to do it as a domain admin.
After the Installation I'd like to do other stuff in the script, but the Installation must be finished before continueing with the script.
When I Wait-Process for the installation to end, I get an access denied error.
Is there a way to wait for a Process to finish, which was started on another user context?
This is my code:
$P = Start-Process $Pfad\vcredist_x64.exe -Argumentlist "/install","/passive","/norestart" `
-WorkingDirectory $Pfad -Credential $cred -PassThru | Wait-Process
This are the error messages (translated from german):
Access denied
Wait-Process : This command terminated the "vcredist_x64 (6284)" operation due to the following error: Access denied. In Zeile:6
Zeichen:84
+ ... -WorkingDirectory $Pfad -Credential $cred -PassThru | Wait-Process
+ ~~~~~~~~~~~~
+ CategoryInfo : CloseError: (System.Diagnost... (vcredist_x64):Process) [Wait-Process], ProcessCommandException
+ FullyQualifiedErrorId : ProcessNotTerminated,Microsoft.PowerShell.Commands.WaitProcessCommand
Timeout issue
Wait-Process : This command terminated the process because the "vcredist_x64 (6284)" process was not completed within the specified timeout. In Zeile:6 Zeichen:84
+ ... -WorkingDirectory $Pfad -Credential $cred -PassThru | Wait-Process
+ ~~~~~~~~~~~~
+ CategoryInfo : CloseError: (System.Diagnost... (vcredist_x64):Process) [Wait-Process], TimeoutException
+ FullyQualifiedErrorId : ProcessNotTerminated,Microsoft.PowerShell.Commands.WaitProcessCommand
Posting my earlier comment as an answer
$proc = Start-Process "Notepad.exe" -PassThru
$proc.WaitForExit()
$proc1 = Start-Process "Calc.exe" -PassThru
$proc1.WaitForExit()
I did something like this the other day. My case was a little different. I was kicking something off, I wanted something to happen once that process ended, in the meantime I was doing another thing. For that, I used an event. Here is an example using Notepad:
$do_other_stuff = {
Write-Host 'Do Other Stuff'
Get-EventSubscriber | Unregister-Event
}
$p = Start-Process notepad.exe -PassThru
...do other stuff...
$job = Register-ObjectEvent -InputObject $p `
-EventName Exited `
-SourceIdentifier notepad `
-Action $do_other_stuff
If I wanted to wait for the even to trigger, I'd use Wait-Event. Stuff whatever you want done once the installer finishes running in the $do_other_stuff script block.
To your question, the following worked for me:
$p = Start-Process notepad.exe -PassThru -Wait
...do stuff...
As did...
$p = Start-Process notepad.exe -PassThru
$p.WaitForExit()
...do stuff...
These cases worked when running the script in elevated and un-elevated context. It worked when passing credentials to Start-Process via the -Credential parameter. I wasn't able to try it where the account calling Start-Process is a low privileged account; I have a meeting in a few, sorry.

Intermittent errors with automation of virtual machines using Powershell

I have an intermittent problem with Powershell when automating some tests on a virtual machine.
The scenario and set up is as follows:
Server running HyperV
One virtual machine with multiple snapshots
Powershell script that restores a given snapshot, copies files over, runs a test and retrieves log files
Batch file that calls the Powershell script multiple times with different parameters
The batch file parameters specify things like which snapshot to use, which test to run, etc.
The problem is as follows:
I can run the batch and some of the tests will fail to copy files / fail to create a scheduled task / fail to retrieve log files / etc. It varies which if any (or all) sections fail. Some of the tests will work completely. If I re-run the same batch file, again some tests may fail and others will work; there is no consistency in terms of which fail and which run. Sometimes I have two adjacent tests that use the same snapshot, 1 will work and 1 won’t (see errors below).
To restore the snapshots I am using the “PowerShell Management Library for Hyper-V” from: (http://pshyperv.codeplex.com/releases)
Below is some of the code:
Powershell (minus a few functions / variable declarations / reading xml config file / reading and validating command line inputs / and other non-relevant sections):
Function ApplySnapshot
{
LogAction "Starting apply snapshot"
LogAction $("Restoring snapshot {0}" -f $ss)
#Stop a running VM, restore snapshot, start it up and connect to it
$vmstate = get-vmstate $vmname
$vmstate = $vmstate.EnabledState
if ($vmstate -ne "Stopped")
{
stop-vm $vmname -force
Start-Sleep -Second 15
}
get-vmsnapshot $vmname | where {$_.ElementName -eq $ss} | Restore-VMSnapshot -force
start-vm $vmname -force
Start-Sleep -Second 20
LogAction $("Snapshot {0} restored" -f $ss)
LogAction "End apply snapshot"
}
Function CopyFiles
{
LogAction "Start copy installation files"
$from = "\\server\folderx"
$to = "\\" + $hostname + "\C$\test"
Enter-PSSession -ComputerName $hostname -Credential $cred
Copy-Item $from $to -Recurse
LogAction "End copy installation files"
}
Function CreateSchedule ($hn, $tn, $tr, $sd, $st, $un, $pw)
{
LogAction "Starting create schedule"
Invoke-Command -ComputerName $hn -ScriptBlock {
param($hn, $tn, $tr, $sd, $st, $un, $pw)
Write-Host $("Host name: [{0}]" -f $hn);
$cmd = $("schtasks.exe /create /S ""{0}"" /tn ""{1}"" /tr ""{2}"" /sc once /sd {3} /st {4} /ru ""{5}"" /rp ""{6}"" /rl highest /V1" -f $hn, $tn, $tr, $sd, $st, $un, $pw);
Invoke-Expression $cmd;
} -ArgumentList #($hn, $tn, $tr, $sd, $st, $un, $pw)
LogAction "End create schedule"
}
...setting variables etc...
ApplySnapshot
CopyFiles
CreateSchedule -hn $hostname -tn $taskname -tr $taskrun -sd $setdate -st $settime -un $username -pw $password
Batch file:
PowerShell -Command "& C:\Auto.ps1" <...params...>
PowerShell -Command "& C:\Auto.ps1" <...params...>
PowerShell -Command "& C:\Auto.ps1" <...params...>
PowerShell -Command "& C:\Auto.ps1" <...params...>
pause
Example output:
C:\Auto>PowerShell -Command "& C:\Auto.ps1" <...params...>
WARNING: The job to Change state of VM TestVM to Stopped is still
running in the background.
You can check its progress with Test-wmiJob or Test-wmiJob -statusOnly using
the following job id:
\\Server\root\virtualization:Msvm_ConcreteJob.InstanceID="A207CEBA-F582-4A42-
BCDE-3312C7FB6DCC"
JobStarted
WARNING: The job to Change state of VM TestVM to Running is still
running in the background.
You can check its progress with Test-wmiJob or Test-wmiJob -statusOnly using
the following job id:
\\Server\root\virtualization:Msvm_ConcreteJob.InstanceID="42C31CEF-00E2-40A7-
AF70-578B0B91B05D"
JobStarted
Enter-PSSession : Connecting to remote server failed with the following error m
essage : The WinRM client cannot complete the operation within the time specifi
ed. Check if the machine name is valid and is reachable over the network and fi
rewall exception for Windows Remote Management service is enabled. For more inf
ormation, see the about_Remote_Troubleshooting Help topic.
At C:\Auto.ps1:192 char:18
+ Enter-PSSession <<<< -ComputerName $hostname -Credential $cred
+ CategoryInfo : InvalidArgument: (TestVM:String) [Enter-PSS
ession], PSRemotingTransportException
+ FullyQualifiedErrorId : CreateRemoteRunspaceFailed
[TestVM] Connecting to remote server failed with the following error messa
ge : The WinRM client cannot complete the operation within the time specified.
Check if the machine name is valid and is reachable over the network and firewa
ll exception for Windows Remote Management service is enabled. For more informa
tion, see the about_Remote_Troubleshooting Help topic.
+ CategoryInfo : OpenError: (:) [], PSRemotingTransportException
+ FullyQualifiedErrorId : PSSessionStateBroken
So, in this example, the snapshot has been successfully applied (despite the warnings). The “Enter-PSSession” error appears after the files have been copied to the virtual machine.
As a test, I tried this on a different server (also running HyperV etc etc), and I found that I still get the initial error (after the file copying stage) but I do not get the error creating the scheduled task.
All my efforts to search for information on the “Connecting to remote server failed with the following error message : The WinRM client cannot complete the operation within the time specified.” Error seem to say “make sure the machine is set up for remote use”; well I know it is because sometimes it works and if I run just an “Enter-PSSession” command by itself, I can connect.
The server(s) and virtual machine(s) are on the same domain.
I know there’s a lot to take in here, but I would really appreciate some help in how to troubleshoot / fix this problem.
Thank you
Maybe the targets are not always up when the connection attempts are being made.

PSSessionStateBroken with start-job -credential in scheduled task

The lines below work fine from a Powershell prompt, but fail from a scheduled task.
$pass = gc C:\secrets\shhh.txt | convertTo-secureString
$Cred = new-object -typeName System.management.automation.psCredential -argumentlist "domain\domainUser",$pass
$path = "\\server\share\folder"
$j = start-job {gci -path $args[0]} -cred $Cred -argumentList $path | wait-job | receive-job
$body = $j | out-string
$error | out-file C:\temp\err.txt
send-mailMessage -to me#domain.tld -from server#domain.tld -subject halp -smtpserver mailserver.domain.tld -body $body
In c:\temp\err.txt the Windows 2008R2 Scheduled Task leaves a breadcrumb:
[localhost] The background process exited abnormally.
+ CategoryInfo : OpenError: (localhost:String) [], PSRemotingTransportException
+ FullyQualifiedErrorId : 2101,PSSessionStateBroken
...which brings us to this Microsoft bug report. The report mentions a workaround with localhost loopback remoting, which sounds kinda dirty. Should I go there?
Is there a better solution? Maybe with one of the *session cmdlets or invoke-command? The scheduled task's Start in value is set, but maybe the background process uses some variable in some bad way, like so?
No luck yet calling powershell.exe with –version 2 or with -command "& {bigKlugeyScriptblock}" syntax.
edit: I'm trying to avoid creating a new domain account to run the task. The task can't run as domainUser from $cred, because that account should not have permissions on localhost.
As some possible work arounds how about:
Put alternate credentials on the scheduled task itself
Using the runas command to start powershell.exe as a different user
Using net use /user parameter to authenticate access to the network path
[/USER:[dotted domain name\]username]
[/USER:[username#dotted domain name]