I want to execute powershell inline , but when I try to execute everything. I got Out-Null : A positional parameter cannot be found that accepts argument 'do'.
$OutputString = 'Hello World'
$WScript = New-Object -ComObject 'wscript.shell'
$WScript.Run('notepad.exe') | Out-Null
do {
Start-Sleep -Milliseconds 100
}
until ($WScript.AppActivate('notepad'))
$WScript.SendKeys($OutputString)
inline powershell
powershell -Command $OutputString = 'Hello World';$WScript = New-Object -ComObject 'wscript.shell';$WScript.Run('notepad.exe') | Out-Null ;do{ Start-Sleep -Milliseconds 100};until($WScript.AppActivate('notepad'));$WScript.SendKeys($OutputString);
I want to get the same result inline , but I got this issue
Your code had 3 problems:
a missing ; before the do statement (multiple statements placed on a single line must be ;-separated in PowerShell)
you've later added that, which makes your command inconsistent with the described symptom.
a missing until keyword after do { ... }
you've later added that, but with a preceding ;, which breaks the do statement.
unescaped use of |, which means that cmd.exe itself interpreted it, not PowerShell as intended (I'm assuming you're calling this from cmd.exe, otherwise there'd be no need to call PowerShell's CLI via powershell.exe).
| must be escaped as ^| to make cmd.exe pass it through to PowerShell.
Fixing these problems yields the following command for use from cmd.exe:
# From cmd.exe / a batch file:
powershell -Command $OutputString = 'Hello World'; $WScript = New-Object -ComObject 'wscript.shell'; $WScript.Run('notepad.exe') ^| Out-Null; do{ Start-Sleep -Milliseconds 100} until ($WScript.AppActivate('notepad')); $WScript.SendKeys($OutputString)
If you do want to run your code from PowerShell, you can just invoke it directly, as shown in the first snippet in your question - there's no need to create another PowerShell process via powershell.exe
If, for some reason, you do need to create another PowerShell process, use the script-block syntax ({ ... }) to pass the command, in which case no escaping is needed:
# From PowerShell
powershell -Command { $OutputString = 'Hello World'; $WScript = New-Object -ComObject 'wscript.shell'; $WScript.Run('notepad.exe') | Out-Null; do{ Start-Sleep -Milliseconds 100} until ($WScript.AppActivate('notepad')); $WScript.SendKeys($OutputString) }
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
The following script demonstrates an issue I'm experiencing after moving from PowerShell v4 to v5.1. I make extensive use of Start-Process redirecting StandardError to a file, if this file contains data after execution I can read the file to see what the issue was.
However, with v5.1 when the launched PowerShell process starts, it makes use of progress output which is on the error stream, so this is redirected to the error file. In previous versions of PowerShell this did not happen, if I use Command rather than EncodedCommand it works as expected.
I believe this is a bug but it would be useful if someone could confirm or suggest a workaround?
The output to the script below is as follows:
PowerShell v4
No Errors
hello world!
PowerShell v5.1
WARNING:
SourceId Record
1 parent = -1 id = 0 act = Preparing modules for first use. stat = cur = pct = -1 sec = -1 type = Completed
hello world!
hello world!
Demo Script
Note: this will create 2 temporary files, which are both deleted at the end.
$outfile = [System.IO.Path]::GetTempFileName()
$errorfile = [System.IO.Path]::GetTempFileName()
$command = "write-host 'hello world!'"
$encodedCommand = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($command))
$argumentList = #(
"-NoLogo",
"-NonInteractive",
"-NoProfile",
"-EncodedCommand",
$encodedCommand
)
Start-Process powershell `
-argumentlist $argumentList `
-PassThru `
-wait `
-RedirectStandardOut $outfile `
-NoNewWindow `
-RedirectStandardError $errorfile | Out-Null
if ((Get-Item $errorfile).length -gt 0)
{
Import-Clixml $errorfile | Out-String | Write-Warning
}
else
{
Write-Host "No Errors"
}
Get-Content $outfile
$outfile, $errorfile | Remove-item
Test.ps1
Param (
[String]$CountryCode,
[String]$FilesPath,
[String]$KeepassDatabase,
[String]$KeepassKeyFile,
[String]$EventLog,
[String]$EventSource
)
Write-Host 'Ok' -ForegroundColor Yellow
Write-Host $PSBoundParameters
Start-Sleep -Seconds 5
The goal is to call the script with named parameters in elevated mode. When using named parameters without $Credential, it works fine. The window pops up and the word Ok is displayed:
$StartParams = #{
ArgumentList = "-File `"Test.ps1`" -verb `"runas`" -FilesPath `"S:\Files`" -CountryCode `"XXX`""
}
Start-Process powershell #StartParams
When I add the Credential argument it also pops-up but I can't see anything:
$StartParams = #{
Credential = Get-Credential
ArgumentList = "-File `"Test.ps1`" -verb `"runas`" -FilesPath `"S:\Files`" -CountryCode `"XXX`""
}
Start-Process powershell #StartParams
Am I missing something super obvious here? Even when using the same credentials as the logged on user, I can't see the text.
You need to specify an absolute path to the file. The new PowerShell-process (which will run as admin) doesn't run in the same working directory as your current session.
Try:
$StartParams = #{
FilePath = "powershell.exe"
Credential = Get-Credential
Verb = "RunAs"
ArgumentList = "-File `"c:\temp\Test.ps1`" -FilesPath `"S:\Files`" -CountryCode `"XXX`""
}
Start-Process #StartParams
If you only know the relative path, use Resolve-Path to convert it. Ex:
ArgumentList = "-NoExit -File `"$(Resolve-Path test.ps1 | Select-Object -ExpandProperty Path)`" -FilesPath `"S:\Files`" -CountryCode `"XXX`""
You should also look into string format or here-string so you can avoid escaping every double quote. It makes your life easier:
#Using here-string (no need to escape double quotes)
ArgumentList = #"
-NoExit -File "$(Resolve-Path test.ps1 | Select-Object -ExpandProperty Path)" -FilesPath "S:\Files" -CountryCode "XXX"
"#
#Using string format
ArgumentList = '-NoExit -File "{0}" -FilesPath "{1}" -CountryCode "{2}"' -f (Resolve-Path test.ps1 | Select-Object -ExpandProperty Path), "S:\Files", "XXX"
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))