PowerShell error Method invocation failed - powershell

I have a strange problem where I have existing code that works on Windows 7x86 PowerShell 2.0 but will not work on Windows 10x86 using PowerShell 5.0.
The error states:
Method invocation failed because
[System.Management.Automation.PSRemotingJob] does not contain a method named 'op_Addition'.
At
C:\Build\AmazingCharts\Working\Building\Installer\WiseInstaller\Obfusated
Projects\ObfuscateFiles.ps1:211
char:13 + $Jarray +=
Start-Job -name ObfuscateFiles $ScriptBlock - ...
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation:
(op_Addition:String) [], Runti
meException +
FullyQualifiedErrorId : MethodNotFound
This is stating that the overloaded operator += doesn't seem to be defined but I find it very difficult to believe that the += overloaded addition operator would be deprecated in PowerShell 5.0 for arrays.
Further, the code seems to still execute and produce a correct output.
In my case I use a jobs array to execute Red Gate Smart Assembly to obfuscate build files. Below is a code snippet
$Jarray = #() #initialize jobs array
foreach ($file in $farray_) {
$params = $file,$cwd,$bldLog #parameters that are passed to script block
$nCount = (get-job -state "Running").Count #determines num of running jobs
if (-not $nCount) {$ncount=0} #initialize variable if null
if ($nCount -le $MAX_INSTANCES) {
#The line below is the one that generates the error
$Jarray += Start-Job -name ObfuscateFiles $ScriptBlock -ArgumentList $params
Start-Sleep -s 1 #gives the system time for process to start
} else {
$nCount = (get-job -state "Running").Count
if (-not $nCount) {$nCount=0}
while ($nCount -ge $MAX_INSTANCES) {
Start-Sleep -s 1 #gives time to recognize the job has started
$nCount = (get-job -state "Running").Count
if (-not $nCount) {$nCount=0}
}
$Jarray += Start-Job -name ObfuscateFiles $ScriptBlock -ArgumentList $params
}
}
When this execute it correctly generates all of the obfuscated files and in a timely manner so I know it is building in parallel up to the MAX_INSTANCES value. I even have code (not included above) that verifies that the number of expected obfuscated files is correct. The 'Method invocation failed' error reports back to CruiseControl.NET and my build fails.
Another frustration is that executing the same .ps1 file in PowerGUI completes successfully without any errors.
I have made sure that my array gets initialized. I have tried suggestions to specify that the array be of type [PSObject] but I get the same results.
Does anyone have any ideas what I can check for this?
Thank you.
EDIT:
I tested this in the PowerShell IDE
-- When tested running from a command prompt, I get the errors
-- When tested running the debugger, I do not get errors
I checked the $Jarray type and it is a System.Object[], which is what I would expect for an array.
I checked the $Jarray value during debug and the jobs are being created but the error still persists as if it doesn't understand the += overloaded Addition operator. My only guess is that this is a bogus error masking something else.
I added a try/catch block around the call to $Jarray. I had tried ErrorAction Ignore but that did not work because the call does not know what to do with the error. The try/catch block successfully masks the error; fortunately I have another method to determine if my call is successful. Needless to say this does not 'solve' the issue, it only masks the issue because right now it is not critical.
I will continue to update this area with things I have tested.

Just an idea, but might want to make sure your new machine has a relaxed policy for remoting AND enabling scripts:
Set-ExecutionPolicy Unrestricted &
Enable-PSRemoting

Related

[System.IO.Path]::GetTempPath() outputs local temp directory when called through Invoke-Command on a remote machine

I'm running PowerShell commands on a remote machine by the use of Invoke-Command -ComputerName. I'm trying to obtain the path of the temporary directory of the remote machine.
Depending on where I call [System.IO.Path]::GetTempPath() it either outputs the expected remote directory C:\Users\…\AppData\Local\Temp or my local temporary directory C:\temp.
This command is not working as expected:
Invoke-Command -ComputerName MyRemoteMachine -ScriptBlock {
Write-Output ([System.IO.Path]::GetTempPath())
}
# Outputs local directory 'C:\temp'
# Expected remote directory 'C:\Users\…\AppData\Local\Temp'
The problem can be reproduced with other commands than Write-Output, e. g. Join-Path.
Contrary, the following code samples all give the expected output of C:\Users\…\AppData\Local\Temp.
Invoke-Command -ComputerName MyRemoteMachine -ScriptBlock {
[System.IO.Path]::GetTempPath()
}
Invoke-Command -ComputerName MyRemoteMachine -ScriptBlock {
$tmp = [System.IO.Path]::GetTempPath(); Write-Output $tmp
}
Invoke-Command -ComputerName MyRemoteMachine -ScriptBlock {
Start-Sleep 1
Write-Output ([System.IO.Path]::GetTempPath())
}
Invoke-Command -ComputerName MyRemoteMachine -ScriptBlock {
Write-Output ([System.IO.Path]::GetTempPath())
Start-Sleep 1
}
Obviously Start-Sleep isn't a solution, but it seems to indicate some kind of timing problem.
Suspecting that the problem isn't limited to GetTempPath() I tried another user-related .NET API, which also unexpectedly outputs my local folder instead of the remote one:
Invoke-Command -ComputerName MyRemoteMachine -ScriptBlock {
Write-Output ([System.Environment]::GetFolderPath([Environment+SpecialFolder]::MyDocuments))
}
How can I use [System.IO.Path]::GetTempPath() and other .NET API in a PowerShell remote session in a predictable way?
Santiago Squarzon has found the relevant bug report:
GitHub issue #14511
The issue equally affects Enter-PSSession.
While a decision was made to fix the problem, that fix hasn't yet been made as of PowerShell 7.3.1 - and given that the legacy PowerShell edition, Windows PowerShell (versions up to v5.1, the latest and final version) will see security-critical fixes only, the fix will likely never be implemented there.
While the linked bug report talks about the behavior originally having been by (questionable) design, the fact that it only surfaces in very narrow circumstances (see below) implies that at the very least that original design intent's implementation was faulty.
The problem seems to be specific to a script block with the following characteristics:
containing a single statement
that is a cmdlet call (possibly with additional pipeline segments)
whose arguments involve .NET method calls, which are then unexpectedly performed on the caller's side.
Workaround:
Make sure that your remotely executing script block contains more than one statement.
A simple way to add a no-op dummy statement is to use $null++:
# This makes [System.IO.Path]::GetTempPath() *locally* report
# 'C:\temp\'
# *Remotely*, the *original* value should be in effect, even when targeting the
# same machine (given that the env. var. modification is process-local).
$env:TMP = 'C:\temp'
Invoke-Command -ComputerName MyRemoteMachine -ScriptBlock {
Write-Output ([System.IO.Path]::GetTempPath()); $null++ # <- dummy statement.
}
Other workarounds are possible too, such as enclosing the cmdlet call in (...) or inserting a dummy variable assignment
(Write-Output ($unused = [System.IO.Path]::GetTempPath()))
Your Start-Sleep workaround happened to work because by definition it too added another statement; but what that statement is doesn't matter, and there's no timing component to the bug.

Resolving Variables within Scriptblocks for Jobs

I have a job setup to run/call a script that outputs a file. The code works fine when run on its own. I have verified the job is not being blocked and completes successfully. However, no file is generated when the script is called from the job. I'm even doing something very similar elsewhere without an issue, in fact it is from another similar set up that I pulled this code to begin with. Anyway, here is what I have:
When run as job, no file output:
$McShieldCheckerJob = Start-Job -ScriptBlock {
E:\ICSScoreCardUtilityPack\ServiceAndProcessCheckerV1.0.ps1 -ServicesAndOrProcesses 'McShield' -Comps 'COMP15' `
-OutputMode 1 -OutputFile $McShieldCheckOutputFileName -GroupByMachine "N" -AsJob "Y"
} -Name McShieldCheckerJob
When run not as a job, file outputs as expected.
E:\ICSScoreCardUtilityPack\ServiceAndProcessCheckerV1.0.ps1 -ServicesAndOrProcesses 'McShield' -Comps 'COMP15' `
-OutputMode 1 -OutputFile $McShieldCheckOutputFileName -GroupByMachine "N" -AsJob "Y"
Stumped as to why this won't work exactly the same as a job vs. not as a job. Again, I have verified via Get-Job and Receive-job that the job is not being blocked and is completing successfully.
EDIT:
It seems the variable $McShieldCheckOutputFileName cannot be resolved within the scriptblock for the job. If I use a string literal [for $McShieldCheckOutputFileName] I do not have the issue. (I was able to determine after a bit more troubleshooting that the lack of an output file when running as a job was due to a null value for my output file name.) So, I can probably work around this easily enough, but still this seems curious that a variable cannot be resolved within the scriptblock? Must be a scope thing...
Figured it out... here is how it is done:
$McShieldCheckerJob = Start-Job -ScriptBlock {
E:\ICSScoreCardUtilityPack\ServiceAndProcessCheckerV1.0.ps1 -ServicesAndOrProcesses 'McShield' -Comps 'COMP15' `
-OutputMode 1 -OutputFile "$($args[0])" -GroupByMachine "N"
} -Name McShieldCheckerJob -ArgumentList $McShieldCheckOutputFileName
As we can see, we need to specify an -Argumentlist to the script block, then reference said arguments within the scriptblock via the $args variable.
Reference (see example 10)

Start-Process, mklink and encrypted/stored credentials, oh my

I am working on a way to create a Symlink as a standard user, to address the situation outlined here.
I have created a password file and an AES key as shown here.
And I have this code, which without the credential stuff, but run from an elevated ISE, works as intended, creating a symlink in the root of C that points to the created folder in root of C.
But, when run unelevated it doesn't create the symlink, nor does it throw an error of any kind. It acts the same as if there was no credentials in use.
$passwordFile = "\\Mac\Support\Px Tools\x_PS Dev\SymLink_password.txt"
$keyFile = "\\Mac\Support\Px Tools\x_PS Dev\SymLink_AES.key"
$user = 'Px_Install'
$key = Get-Content $keyFile
$credential = New-Object -typeName:System.Management.Automation.PSCredential -argumentList:#($user, (Get-Content $passwordFile | ConvertTo-SecureString -key:$key))
if (-not (Test-Path 'C:\_Real')) {
New-Item 'C:\_Real' -itemType:directory > $null
}
if (-not (Test-Path 'C:\_Real\real.txt')) {
New-Item 'C:\_Real\real.txt' -itemType:file > $null
}
try {
Start-Process -filePath:cmd.exe -windowStyle:hidden -argumentList:('/c', 'mklink', '/d', 'C:\C4RLink', "`"C:\_Real`"") -credential:$credential -errorAction:stop
} catch {
Write-Host "Error"
}
So, three questions I guess.
1: Is there any way to test the validity of the created credential? I used $credential.GetType and it returns
OverloadDefinitions
-------------------
type GetType()
Which may or may not be correct, not sure.
2: Is there something wrong with my use of Start-Process?
3: Is there a way to actually trap meaningful errors or is cmd.exe so primitive I am stuck checking to see if the link exists post Start-Process and throwing my own error?
I tried
$results = Start-Process -filePath:cmd.exe -windowStyle:hidden -argumentList:('/c', 'mklink', '/d', 'C:\C4RLink', "`"C:\_Real`"") -credential:$credential -errorAction:stop -passThru
Write-Host "$results"
and it produces System.Diagnostics.Process (cmd) which isn't so helpful.
Speaking of Windows 7, I just tested it in Windows 7/PS2.0, and it DOES throw an error, but in Windows 10 it doesn't. Gawd Micros0ft, can't you get your shit together, EVER? but, maybe a thread to follow. Also going to try getting credentials another way, to eliminate that variable.
FWIW, I tried NOT wrapping the argument list in an array, in fact I started with that. But it didn't work so I tried the array on a lark.
EDIT: So, trying it in Windows 7 does produce an error, which is Parameter set cannot be resolved using the specified named parameters. I also realized I needed -verb:Runas in there. Added that, and switched my credentials to use Get-Credential for now. But still getting parameter set issues. Sigh.
Edit2: Seems to not like -verb or -windowsStyle in Windows 7/PS2.0. The latter is no big deal I guess, but -verb is pretty much required to get this to work methinks.
Edit3: nope, seems not to like -verb in Windows 10 either. But I have it reporting exceptions now, so thats a form of progress.
EDIT4: getting closer. I now have this
Start-Process powershell -credential (Get-Credential 'Px_Install') -argumentList "-noprofile -command &{Start-Process -filePath cmd.exe -argumentList '/c', 'mklink', '/d', 'C:\C4RLink', 'C:\_Real' -verb runas}"
And it works, but it raises a UAC dialog, which pretty much makes it useless.

Stop a process running longer than an hour

I posted a question a couple ago, I needed a powershell script that would start a service if it was stopped, stop the process if running longer than an hour then start it again, and if running less than an hour do nothing. I was given a great script that really helped, but I'm trying to convert it to a "process". I have the following code (below) but am getting the following error
Error
"cmdlet Start-Process at command pipeline position 3
Supply values for the following parameters:
FilePath: "
Powershell
# for debugging
$PSDefaultParameterValues['*Process:Verbose'] = $true
$str = Get-Process -Name "Chrome"
if ($str.Status -eq 'stopped') {
$str | Start-Process
} elseif ($str.StartTime -lt (Get-Date).AddHours(-1)) {
$str | Stop-Process -PassThru | Start-Process
} else {
'Chrome is running and StartTime is within the past hour!'
}
# other logic goes here
Your $str is storing a list of all processes with the name "Chrome", so I imagine you want a single process. You'll need to specify an ID in Get-Process or use $str[0] to single out a specific process in the list.
When you store a single process in $str, if you try to print your $str.Status, you'll see that it would output nothing, because Status isn't a property of a process. A process is either running or it doesn't exist. That said, you may want to have your logic instead check if it can find the process and then start the process if it can't, in which case it needs the path to the executable to start the process. More info with examples can be found here: https://technet.microsoft.com/en-us/library/41a7e43c-9bb3-4dc2-8b0c-f6c32962e72c?f=255&MSPPError=-2147217396
If you're using Powershell ISE, try storing the process in a variable in the terminal, type the variable with a dot afterwards, and Intellisense (if it's on) should give a list of all its available properties.

Jenkins powershell plugin always builds successfully

I'm using Jenkins PowerShell plugin to build a project.
However, I found that Jenkins always considers my build successful no matter what I type inside Windows PowerShell command.
Here's an example:
As you can see, asdf isn't a legal command. Jenkins should give me FAILURE after the build.
But the console output gives me:
Started by user admin
Building in workspace C:\Users\Administrator\.jenkins\jobs\Test\workspace
[workspace] $ powershell.exe -NonInteractive -ExecutionPolicy ByPass "& 'C:\Users\ADMINI~1\AppData\Local\Temp\hudson2092642221832331776.ps1'"
The term 'asdf' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
At C:\Users\ADMINI~1\AppData\Local\Temp\hudson2092642221832331776.ps1:1 char:5
+ asdf <<<<
+ CategoryInfo : ObjectNotFound: (asdf:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
Finished: SUCCESS
I think the execution result of PowerShell should depend on $lastexitcode.
Is this a bug of PowerShell plugin?
As of 1.3, the plugin will not handle exceptions such as those from missing commands. You can do this yourself with try/catch:
try
{
asdf
}
catch
{
write-host "Caught an exception"
exit 1
}
See MSDN for more.
For me, I wanted the script to stop and fail in Jenkins soon as it hit an error. This was accomplished by adding this to the start of the script:
$ErrorActionPreference = "Stop"
This is discussed here: [How to stop a PowerShell script on the first error?][1]
[1]: How to stop a PowerShell script on the first error?. ..................
Per the latest version of the plugin (Version 1.3 Sept 18 2015), you must use $LastExitCode to fail a build.
Version 1.3 (Sept 18 2015)
PowerShell now runs in Non-Interactive mode to prevent interactive prompts from hanging the build
PowerShell now runs with ExcecutionPolicy set to "Bypass" to avoid execution policy issues
Scripts now exit with $LastExitCode, causing non-zero exit codes to mark a build as failed
Added help and list of available environment variables (including English and French translations)
I want to add here that I just ran into a quirk: you must have the powershell script end with exit and not return.
My jenkins pipe looked like:
script {
result = powershell(returnStatus: true, script: '''...if(error condition) { return 1 }''')
if(result) { error }
}
I was using if(error condition) { return 1 } and while the 1 was showing up as the return value in the jenkins console, it was not failing the build. When i used if(error condition) { exit 1 }, the build failed as expected.
I think this is a helpful addition to this thread - the need to use exit and not return. But I don't understand this part: the pipe is checking for result to be non-zero. What is the difference between exit and return in a powershell directive that makes if(result) { error } not work as expected when using return?
Update Feb-16-2021: Coming back to this to add some more notes from my experience: I think what I was doing and what a lot of people do that confuses things, is to use returnStdout or returnStatus and not check them and/or not fully understand what's coming back.
If you use either of those params, Jenkins will not do anything for you based on their value. You have to check them yourself and act accordingly. On the other hand, if you don't use them, Jenkins will recognize a failure code and fail the pipe if one comes back.
Think about it: If you set returnStatus, you're getting the exit code back from the step as a return value and not as something for Jenkins itself to worry about. If you set returnStdout, you're getting the stdout stream - and not any error codes or anything from stderr. So you must check the return for what you want or you will not get the behavior you are expecting.
What I've been doing for a while is actually not setting either of those params and making sure to set $ErrorActionPreference = 'Stop' at the start of any and all PowerShell scripts running in my pipes. That way any powershell failures automatically fail the pipe as expected, without having to check it.
This is how I implemented RRIROWER's solution. Hope it helps.
<yourscript>.ps1; exit $lastexitcode
Make sure your powershell scripts does exit with the desired value.
Run "exit <value>" as the last line.
Ultimately, I had to resort to the following configuration in Jenkins as none of the solutions here worked for me. Chris Nelson's answer got me on the right track. We're invoking chef-client remotely so we had to do a little magic to get the remote PS session to talk the local and then pass status on to Jenkins.
$res gives the output of chef-client.
$lastsuccess is true or false according to PS rules of engagment.
Of course, you'll have to supply your own evironment variables! :)
Write-host "Deploying $env:Computer with $env:Databag data bag... "
$secstr = ConvertTo-SecureString $env:Password -AsPlainText -Force
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $env:User, $secstr
$s = New-PSSession -ComputerName $env:Computer -Credential $cred
$res = Invoke-Command -Session $s -ScriptBlock { try {chef-client} catch {exit 1}}
$lastsuccess = Invoke-Command -Session $s -ScriptBlock {$?}
Remove-PSSession $s
write-host " --- "
write-host $res
write-host " --- "
if($lastsuccess)
{
write-host "chef deployment completed"
exit 0
}
write-host "chef deployment had errors"
exit 1