I have a powershell script A.ps1 as below:
param([switch] $p1, [switch] $p2)
if ($p1.IsPresent)
{
$p1 = 2
& $B -onboardingId "" -p1 $p1.IsPresent $message = "Deployment In Progress" -connectionString $connectionString
}
elseif ($p2.IsPresent)
{
$p2 = 3
& $B -onboardingId "" -p2 $p2.IsPresent $message = "Deployment In Progress" -connectionString $connectionString
}
The script A.ps1 is calling B.ps1 from itself. Both the scripts are placed in the same path. But I am getting the error below when I try to call script:
The expression after '&' in a pipeline element produced an object that was not valid. It must result in a command name, a script block, or a CommandInfo object.
I tried -ArgumentList but it did not work out for me.
I tried to resolve the issue by referring online but I was not able to resolve it. Can I please get help on this?
Thank you.
Related
I am writing a PowerShell script and I have another PowerShell script.
I know, we can use below code if it is stored in the path
$scriptPath = "D:\Ashish\powershell\script.ps1"
$argumentList = "asdf fgh ghjk"
$output =Invoke-Expression "& `"$scriptPath`" $argumentList"
but my PowerShell is stored in the object instead of a file. I am using the below code
$argumentList = "asdf fgh ghjk"
$logPath = "C:\AshishG\powershell\script21.txt"
$x = 'Write-Host "Hello script2" return "script2"' #This is my powershell script
//Write code here to call this script($x) with params and store the return value in the other object and also store the logs in $logpath
The one way could be to store the PowerShell to the script.ps1 but I think, there should be some way to call it from the PowerShell object itself?
Please share your suggestions.
Seems like you're looking for a script block:
$argumentList = "asdf fgh ghjk"
$logPath = "C:\AshishG\powershell\script21.txt"
$x = {
param($arguments, $path)
"Arguments: $arguments"
"Path: $path"
}
A script block can be executed using the call operator &:
& $x -arguments $argumentList -path $logPath
Or the dot sourcing operator .:
. $x -arguments $argumentList -path $logPath
.Invoke(..) method works too however it's not commonly used and not recommended in this context. See this answer for more information:
$x.Invoke($argumentList, $logPath)
Yet another option is to call the [scriptblock]::Create(..) method, if the script is stored in strings this is the recommended alternative over Invoke-Expression which should be avoided.. This is also very useful for example when we need to pass a function to a different scope. Thanks #mklement0 for the reminder on this one :)
$argumentList = "asdf fgh ghjk"
$logPath = "C:\AshishG\powershell\script21.txt"
$x = #'
"Arguments: $argumentList"
"Path: $logPath"
'#
& ([scriptblock]::Create($x))
# Or:
$scriptblock = [scriptblock]::Create($x)
& $scriptblock
I want to call a "PS App Deployment Toolkit"-package (Link) from a PowerShell-Script with arguments.
The mentioned "PS App Deployment Toolkit"-package is a powershell-script, which I want to call with parameters. (Call a .ps1 from a .ps1)
I want to use splatting for the parameters.
I want to wait for the script to end.
I want to get the exit-code from the script.
Here is my code, which is not working:
$PSADToolKitInstallWrapper = "C:\Temp\MyPackage\PS-AppDeploy.ps1"
$PSADToolKitParameters = #{
"DeploymentType" = "Uninstall";
"DeployMode" = "Interactive";
"AllowRebootPassThru" = $True;
"TerminalServerMode" = $False;
"DisableLogging" = $False;
}
$InstallStatus = Start-Process -FilePath "PowerShell.exe" -ArgumentList $PSADToolKitInstallWrapper #PSADToolKitParameters -Wait -PassThru
Write-Host "Exit-Code: $( $InstallStatus.ExitCode )"
This Line would work fine, but I want to set the Parameters like in the example above:
$InstallStatus = Start-Process -FilePath "PowerShell.exe" -ArgumentList "$PSADToolKitInstallWrapper","-DeploymentType Install -DeployMode Silent -AllowRebootPassThru -TerminalServerMode" -Wait -PassThru
Could you please assist me to get this working?
Thank you!
I don't think you need to try so hard. Why run powershell.exe from inside a PowerShell script? You're already running PowerShell. Just run the command line you want:
$PSADToolKitParameters = #{
"DeploymentType" = "Uninstall"
"DeployMode" = "Interactive"
"AllowRebootPassThru" = $True
"TerminalServerMode" = $False
"DisableLogging" = $False
}
C:\Temp\MyPackage\PS-AppDeploy.ps1 #PSADToolKitParameters
If the path and/or filename to the script you want to run contains spaces, then call it with the invocation operator (&) and quote the filename; example:
& "C:\Temp\My Package\PS-AppDeploy.ps1" #PSADToolKitParameters
Checking the results of the script depends on what the script returns. If it returns an output object, then you can simply assign it:
$output = C:\Temp\MyPackage\PS-AppDeploy.ps1 ...
If the script runs an executable that sets an exit code, you check the value of the $LASTEXITCODE variable (this is analogous to the %ERRORLEVEL% dynamic variable in cmd.exe).
I know about PowerShell jobs, but I want to use start-process and pass a serializable object to the new powershell process. Is there any way to do this?
It seems that using start-process you have to provide a string argument list which won't cut it for me. I'm trying to get a PSCredential from one process to another (or a SecureString, I'll take either one). Maybe this circumvents security.
UPDATE - adding the solution I used after seeing help from others (using solution from #PetSerAl)
I wrote two test scripts: a parent script and a child script. The parent script calls the child script.
Parent Script:
$securePassword = ConvertTo-SecureString "testpassword" -AsPlainText -Force
$cred = New-Object PSCredential("testuser", $securePassword)
$credSerial = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes([Management.Automation.PSSerializer]::Serialize($cred)))
$psFile = "C:\repos\Test\PowerShell Scripts\KirkTestChild.ps1"
$p1 = "-someparam ""this is a test!!!"""
$p2 = "-cred ""$credSerial"""
$proc = Start-Process PowerShell.exe -PassThru:$true -Argument "-File ""$($psFile)""", $p1, $p2
Write-Host "ID" $proc.Id
Write-Host "Has Exited" $proc.HasExited
Start-Sleep -Seconds 15
Write-Host "Has Exited" $proc.HasExited
Child Script:
Param(
$someParam,
$cred
)
Write-Host "someParam: $($someParam)"
Write-Host "cred (raw): $($cred)"
$realCred=[Management.Automation.PSSerializer]::Deserialize([Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($cred)))
Write-Host "cred user: $($realCred.UserName)"
Write-Host "start"
Start-Sleep 5
Write-Host "ending"
Start-Sleep 5
Yes. As PetSerAl wrote in a comment, you can use the PSSerializer class to handle that:
$ser = [System.Management.Automation.PSSerializer]::Serialize($credential)
$cred = [System.Management.Automation.PSSerializer]::Deserialize($ser)
I don't know if your process will understand the CliXML format though; you don't specify what process you're starting.
Since this will produce XML which is multi-line, it may not work to pass this to a process as a parameter which is why PetSerAl is including the code to Base64 encode the resulting XML.
You might also be able to pass the XML over STDIN instead of Base64 encoding, which also ensures that you won't accidentally hit the command line size limit.
You can pass a scriptblock as an argument from a parent PS process to a child PS process. The result of the child process executing the script is serialized and returned to the parent. (Run powershell -? to get help on this feature). So if the direction you want to move the credential is from child to parent then this is an alternative, or if you want to move data in both directions, then you could combine the previous answers with this approach. (The OP doesn't say which direction).
EDIT
I'm assuming OP wants to start another Powershell process - because he said "new Powershell process". And he wants to pass a PSCredential from either the parent to the child or vice versa. I'd guess the former. Based on PetSerAl's solution he could serialize the cred as CliXML. It would have new lines. Besides potentially causing problems with passing the arguments to Powershell, the newlines will cause the Deserialize to fail if they are in the middle of a value. So to avoid those problems the whole script can be base64 encoded and passed via the -EncodedCommand parameter. It'll look something like this:
$xmlStr = [management.automation.psserializer]::Serialize($cred)
$scriptStr = "$liveCred = " +
"[management.automation.psserializer]::Deserialize('$xmlstr');" +
"# PS statements to do something with $liveCred"
$inB64 = [Convert]::ToBase64String( [Text.Encoding]::UTF8.GetBytes($scriptStr))
powershell -EncodedCommand $inB64
If OP needs something back from the script that ran in the child PS, and wanted to use the scriptblock feature mentioned earlier in this answer, I don't he could use this approach, because that feature is related to the -Command parameter. Rather newlines would need to be removed and escaping concerns might come into play.
Here's a variation that uses a ScriptBlock instead of script files. See the post by Greg Bray at Powershell Start-Process to start Powershell session and pass local variables.
$securePassword = ConvertTo-SecureString 'testpassword' -AsPlainText -Force
$cred = New-Object PSCredential( 'testuser', $securePassword )
$scriptBlockOuter = {
$sb = {
Param(
[Parameter( Mandatory, Position = 0 )]
[String] $someParam,
[Parameter( Mandatory, Position = 1 )]
[String] $credSerial
)
$cred = [System.Management.Automation.PSSerializer]::Deserialize( [System.Text.Encoding]::UTF8.GetString( [System.Convert]::FromBase64String( $credSerial )))
Write-Host "someParam: $someParam"
Write-Host "cred user: $($cred.UserName)"
Write-Host 'start'
Start-Sleep 5
Write-Host 'ending'
Start-Sleep 5
}
}
$p1 = 'this is a test!!!'
$credSerial = [System.Convert]::ToBase64String( [System.Text.Encoding]::UTF8.GetBytes( [System.Management.Automation.PSSerializer]::Serialize( $cred )))
$proc = Start-Process PowerShell -PassThru -ArgumentList '-Command', $scriptBlockOuter, '& $sb', '-someParam', "'$p1'", '-credSerial', "'$credSerial'"
Write-Host 'ID' $proc.Id
Write-Host 'Has Exited' $proc.HasExited
Start-Sleep -Seconds 15
Write-Host 'Has Exited' $proc.HasExited
Using this ConvertTo-Expression cmdlet:
Parent script:
$Expression = $Cred | ConvertTo-Expression
$credSerial = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($Expression))
Child script:
$Expression = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($credSerial))
$Cred = Invoke-Expression $Expression # or safer: &([ScriptBlock]::Create($Expression))
I'd like to run a command such as:
pushd \\myServer\share\scripts
myBatchFile.bat param1 param2 "parameter 3"
popd
Only initiating through powershell.
NB: The name of the batch file is held in a variable, as are each of the parameters.
function Escape-StringForCmd($a)
{
if(($a -like '*"*') -or ($a -like '* *'))
{
('"{0}"' -f ($a -replace '"','""'))
}
else
{
$a
}
}
$batch = "myBatchFile.bat"
$p1 = "param1"
$p2 = "param2"
$p3 = "parameter 3"
$batch = Escape-StringForCmd($batch)
$p1 = Escape-StringForCmd($p1)
$p2 = Escape-StringForCmd($p2)
$p3 = Escape-StringForCmd($p3)
pushd \\myServer\share\scripts
cmd.exe /c $batch $p1 $p2 $p3
#above fails; no error returned; I think because cmd doesn't like the UNC path, so reverts to the system directory
Start-Process "cmd.exe" -ArgumentList "/c",$batch,$p1,$p2,$p3 -NoNewWindow -Wait -WorkingDirectory "\\myServer\share\scripts"
#above also fails; not sure why as looks healthy when running outside of ps1 file
popd
I've also interested in capturing the output - though as at present the batch file's not being run I'll focus on that initially.
I've not yet tried the ProcessStartInfo solution (see link below) as it seems start-process, or simply cmd.exe /c should work (certainly when I've run tests outside of a ps1 file this has worked), but I'll resort to trying that method shortly.
ProcessStartInfo solution: Powershell: Capturing standard out and error with Process object
Using #JNK's answer along with the below hack, I found a way to get this to work
$tempBatchName = ".\~myTempBatchFile.bat" #put this in a variable so we can easily amend if required
"
pushd \\myServer\share\scripts
$batch $p1 $p2 $p3
popd
" | out-file $tempBatchName -encoding ascii
$MyCmd = ("{0} *>&1" -f $tempBatchName)
$ReturnOutput = Invoke-Expression $MyCmd
$ReturnOutput | out-file ("{0}.log" -f $tempBatchName)
remove-item $tempBatchName
Is there a reason you can't use invoke-expression for this?
$MyCmd = "$batch $p1 $p2 $p3 *>&1"
$ReturnOutput = Invoke-Expression $MyCmd
The *>&1 puts all output from the StdErr and StdOut to the output stream.
More info on redirection operators here.
How do you call a PowerShell script which takes named arguments from within a PowerShell script?
foo.ps1:
param(
[Parameter(Mandatory=$true)][String]$a='',
[Parameter(Mandatory=$true)][ValidateSet(0,1)][int]$b,
[Parameter(Mandatory=$false)][String]$c=''
)
#stuff done with params here
bar.ps1
#some processing
$ScriptPath = Split-Path $MyInvocation.InvocationName
$args = "-a 'arg1' -b 2"
$cmd = "$ScriptPath\foo.ps1"
Invoke-Expression $cmd $args
Error:
Invoke-Expression : A positional parameter cannot be found that accepts
argument '-a MSFT_VirtualDisk (ObjectId =
"{1}\\YELLOWSERVER8\root/Microsoft/Windo...).FriendlyName -b 2'
This is my latest attempt - I've tried multiple methods from googling none seem to work.
If I run foo.ps1 from the shell terminal as ./foo.ps1 -a 'arg1' -b 2 it works as expected.
After posting the question I stumbled upon the answer. For completeness here it is:
bar.ps1:
#some processing
$ScriptPath = Split-Path $MyInvocation.InvocationName
$args = #()
$args += ("-a", "arg1")
$args += ("-b", 2)
$cmd = "$ScriptPath\foo.ps1"
Invoke-Expression "$cmd $args"
Here is something that might help future readers:
foo.ps1:
param ($Arg1, $Arg2)
Make sure to place the "param" code at the top before any executable code.
bar.ps1:
& "path to foo\foo.ps1" -Arg1 "ValueA" -Arg2 "ValueB"
That's it !