PowerShell removes multiple consecutive whitespaces when I pass arguments to a nested Start-Process command - windows-10

This powershell code works fine:
powershell -NoProfile -Command {Start-Process -FilePath wscript.exe -Verb RunAs -ArgumentList '"C:\Users\TestAccount\Desktop\cartella con spazi\test.vbs" "/CurrentDirectory:C:\Users\TestAccount\Desktop\Cartella con spazi" "/AppData:C:\Users\TestAccount\AppData\Roaming"'}
But when I enter the complete command, powershell leaves only one space between the words but in my path there are two consecutive ones:
Start-Process -FilePath powershell.exe -WorkingDirectory "$env:ALLUSERSPROFILE" -Credential $credential -WindowStyle Hidden -ArgumentList "-NoProfile -Command & {Start-Process -FilePath wscript.exe -Verb RunAs -ArgumentList '\`"C:\Users\TestAccount\Desktop\cartella con spazi\test.vbs\`" \`"/CurrentDirectory:C:\Users\TestAccount\Desktop\Cartella con spazi\`" \`"/AppData:C:\Users\TestAccount\AppData\Roaming\`"'}"
Same error with this:
Start-Process -FilePath powershell.exe -WorkingDirectory "$env:ALLUSERSPROFILE" -Credential $credential -WindowStyle Hidden -ArgumentList "-NoProfile -Command Start-Process -FilePath wscript.exe -Verb RunAs -ArgumentList '\`"C:\Users\TestAccount\Desktop\cartella con spazi\test.vbs\`" \`"/CurrentDirectory:C:\Users\TestAccount\Desktop\Cartella con spazi\`" \`"/AppData:C:\Users\TestAccount\AppData\Roaming\`"'"
This is the error message:
As you can see, in the error message the path contains only one space between words, while in the code I have correctly entered two consecutive ones.
This is the code to enter the credentials (just before the line that gives an error):
$username = 'Username'
$password = 'Password'
$securePassword = ConvertTo-SecureString $password -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential $username, $securePassword
I've been getting a single line of code to work properly for days but without success.
Those example paths I entered are actually variables (with an indefinite number of consecutive spaces) and therefore by enclosing -ArgumentList in single quotes, the variables are not expanded.
You can adopt the strategy you think is most appropriate to help me.
Windows 10 Pro 64-bit
Powershell Version: 5.1.19041.1237 (Integrated in Windows 10).
UPDATE:
#mklement0 There is the problem that in reality the line in question contains an expandable variable:
Start-Process -FilePath powershell.exe -WorkingDirectory "$env:ALLUSERSPROFILE" -Credential $credential -WindowStyle Hidden -ArgumentList #"
-NoProfile -Command "Start-Process -FilePath wscript.exe -Verb RunAs -ArgumentList '\"$PWD\test.vbs\" \"/CurrentDirectory:$PWD\" \"/AppData:$env:APPDATA\"'"
"#
so if the path contains single quotes, the line goes in error.
UPDATE 2:
#mklement0 Your solution has stopped working since I put it in a script, which is what I need, with the following error message:

Note: This answer addresses the Start-Process problem; for the unrelated problem with .ps1 files whose paths have spaces in them failing to run when they are double-clicked in File Explorer, see this answer.
Try the following, with the arguments passed verbatim:
Start-Process `
-FilePath powershell.exe `
-WorkingDirectory "$env:ALLUSERSPROFILE" `
-Credential $credential `
-WindowStyle Hidden `
-ArgumentList #'
-NoProfile -Command "Start-Process -FilePath wscript.exe -Verb RunAs -ArgumentList '\"C:\Users\TestAccount\Desktop\cartella con spazi\test.vbs\" \"/CurrentDirectory:C:\Users\TestAccount\Desktop\Cartella con spazi\" \"/AppData:C:\Users\TestAccount\AppData\Roaming\"'"
'#
The -Command argument is enclosed in "..." in order to preserve the command-internal spaces as-is.
The embedded " characters in the command string are escaped as \"
The outer -ArgumentList uses a here-string to simplify the embedded quoting.
The verbatim form (#'<newline>...<newline>'#) is used here; use the _expandable form, #"<newline>...<newline>"#, if you need string interpolation (the ability to embed variable references and expressions) - see next section.
While PowerShell-internally it is `, the backtick, that serves as the escape character, PowerShell's CLI expects " characters to be \-escaped on the command line, so as to conform to the most widely used escaping convention.
The cross-platform PowerShell (Core) 7+ edition now also accepts "" as an alternative to \", another common convention on Windows.
While you may situationally have to combine the two escaping approaches, i.e. to use `\" in order to quote a " first for the CLI and then for PowerShell-internal use, in the context of interpreting a -Command argument, this isn't needed in the command above, because the inner -ArgumentList argument uses single-quoting, inside of which " can be be used as-is.
Generalized solution, with arguments provided via variables:
The assumption is that the variable whose value are to be used are named $dir1, $dir2, and $dir3 (and that their values have no embedded " chars., but " aren't supported in file-system paths on Windows anyway).
Start-Process `
-FilePath powershell.exe `
-WorkingDirectory "$env:ALLUSERSPROFILE" `
-Credential $credential `
-WindowStyle Hidden `
-ArgumentList #"
-NoProfile -Command "Start-Process -FilePath wscript.exe -Verb RunAs -ArgumentList '\"$($dir1 -replace "'", "''")\" \"/CurrentDirectory:$($dir2 -replace "'", "''")\" \"/AppData:$($dir3 -replace "'", "''")\"'"
"#
The interpolating form of a here-string (#"<newline>...<newline>"#) must now be used for the outer -ArgumentList argument.
Since the variable values are interpolated inside what powershell.exe will see as a single-quoted string ('...'), any ' characters embedded in the variable values must be escaped as '', which is what -replace "'", "''" in the embedded $(...) subexpressions does.
Optional reading: Troubleshooting calls to external programs:
It can be difficult to diagnose problems with calls to external programs, especially in a nested call such as this. The following is a Windows solution; see the bottom for a Unix solution.
Preparation: Create helper executable echoArgsPause.exe:
The following creates a helper executable, echoArgsPause.exe, which echoes the full command line it was called with, as well as the individual arguments it has parsed the command line into, and then waits for a keystroke.
Note:
As a .NET-based *.exe, it uses .NET's rules for command-line parsing, which in turn is based on Microsoft's C/C++ conventions. Unfortunately, not all executables are guaranteed to use the same conventions. And, indeed, the WSH CLIs (cscript.exe and wscript.exe) do not support embedded " characters in arguments, for instance.
Run the code from Windows PowerShell, because the executable must be compiled via .NET Framework, not .NET (Core), because only the former reports the true, raw command line via [Environment]::CommandLine.
For later reuse, place the resulting .\echoArgsPause.exe file in a directory in your PATH (in a directory listed in $env:PATH).
To create a variant that doesn't wait for a keystroke, remove the Console.Write("Press a key to exit: "); and Console.ReadKey(true); lines and change -OutputAssembly .\echoArgsPause.exe to, say, -OutputAssembly ./echoArgs.exe
Add-Type -OutputType ConsoleApplication -OutputAssembly .\echoArgsPause.exe -TypeDefinition #'
using System;
static class ConsoleApp {
static int Main(string[] args) {
Console.WriteLine("\nraw: [{0}]\n", Environment.CommandLine);
for (int i = 0; i < args.Length; ++i) {
Console.WriteLine("arg #{0}: [{1}]", i, args[i]);
}
Console.Write("Press a key to exit: ");
Console.ReadKey(true);
return 0;
}
}
'#
Use of echoArgsPause.exe to troubleshoot a call
The following, self-contained sample code uses a simplified version of your command, and uses the echoArgsPause.exe in lieu of your target executable (wscript.exe) to print the command line and the parsed arguments.
# Specify the full path to the helper executable.
# Here I'm assuming it is in the current dir.
$echoArgsPausePath = "$($PWD.ProviderPath)\echoArgsPause.exe"
# Sample variable values.
$dir1='c:\temp''o 1'
$dir2='c:\temp 2'
$dir3='c:\temp 3'
# Call your command, with echoArgsPause.exe in lieu
# of the target executable (wscript.exe)
Start-Process `
-FilePath powershell.exe `
-WorkingDirectory "$env:ALLUSERSPROFILE" `
-WindowStyle Hidden `
-ArgumentList #"
-NoProfile -Command "Start-Process -FilePath "$echoArgsPausePath" -Verb RunAs -ArgumentList '\"$($dir1 -replace "'", "''")\" \"/CurrentDirectory:$($dir2 -replace "'", "''")\" \"/AppData:$($dir3 -replace "'", "''")\"'"
"#
On Unix-like platforms:
Unix-like platforms have no process-level command line; instead, they are launched via an array of verbatim arguments.
Therefore, a simple solution is to use the standard printf utility to list each argument received on its own line; e.g.:
printf %s\n foo 'c:\temp''o 1' '3" of snow'
Output up to PowerShell (Core) 7.2.x (broken! note the missing "):
foo
c:\temp'o 1
3 of snow
Output in v7.3.0+:
foo
c:\temp'o 1
3" of snow
The 7.2.x behavior reveals a long-standing bug with respect to passing arguments with embedded double quotes, which was fixed in 7.3.0; it is in effect by default on Unix-like platforms, but will be opt-in on Windows, at some point post-v7.3.1 - see this answer.

I have found another solution without Here-String:
$username = 'Username'
$password = 'Password'
$securePassword = ConvertTo-SecureString $password -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential $username, $securePassword
Start-Process -FilePath powershell -WorkingDirectory "$env:ALLUSERSPROFILE" -Credential $credential -WindowStyle Hidden -ArgumentList "-NoProfile -Command `"Start-Process -FilePath wscript -Verb RunAs -ArgumentList '\`"$((Get-Location).Path -replace "'", "''")\test.vbs\`" \`"/CurrentDirectory:$((Get-Location).Path -replace "'", "''")\`" \`"/AppData:$($env:APPDATA -replace "'", "''")\`"'`""

Related

powershell psobject showing as string instead of psobject, how to convert back to psobject

I have the following variable $Obj set to the following string value:
$Obj = '#{Version=1; Name=a;}'
How do I convert this value from a string into a custom psobject?
I would like to be able to call
$Obj.Version and get the value 1. Currently this call returns nothing.
Note: Due to how I am retrieving this variable, I can't initialize it without the single quotes.
Edit:
Here is the current code:
$Command = "script.ps1 -ExtraInfo $_"
Write-Host $Command
Invoke-Expression -Command $Command
where $_ is #{Version=1; Name=a;} (without the quote)
Originally this code was written as
. script.ps1 -ExtraInfo $_
and worked, but when I added unit tests I changed it to use Invoke-Expression so that it could be testable with Pester unit tests. Is there a better way?
Edit2:
Turns out this can be solved by putting a back tic ` before the expression and that solves the issue for me. Thank you everyone for your input.
$Command = "script.ps1 -ExtraInfo `$_"
Write-Host $Command
Invoke-Expression -Command $Command
The stringified version of a [pscustomobject] instance, which resembles a hashtable literal, is not suitable for programmatic processing, as the following example demonstrates:
# Stringify a [pscustomobject] instance.
PS> "$([pscsutomobject] #{ Version=1; Name='a value' })"
#{Version=1; Name=a value} # !! Quoting of the .Name value was lost
The problem gets worse for property values that are themselves complex objects.
Since you do appear to have access to the original [pscustomobject] instance, the solution is not to stringify.
For that, you simply need to avoid up-front string interpolation by using a verbatim (single-quoted) string literal ('...') and letting Invoke-Expression - which should generally be avoided - interpret the $_ variable as its original type:
# Use *single* quotes to prevent up-front expansion.
$Command = 'script.ps1 -ExtraInfo $_'
Write-Host $Command
Invoke-Expression -Command $Command
Note that the use of a verbatim (non-interpolating) string literal makes the use of Invoke-Expression safe here, though, as Santiago Squarzon points out, there may be a better alternatives in general, and particularly in the context of Pester.
A script-block-based solution ({ ... }) that receives the object as an argument:
$Command = { script.ps1 -ExtraInfo $args[0] }
Write-Host "Calling { $Command } with argument $_"
. $Command $_
This doesn't work with Name=a because a is not a known object (or at least not defined in my PS Session). But if this is a string, this can be done with the following script:
$Obj = '#{Version=1; Name="a";}'
$s= [System.Management.Automation.ScriptBlock]::Create("New-Object -TypeName PSObject -Property $Obj")
$o = Invoke-Command -ScriptBlock $s
$o.Version
As I stated in my comment, this is odd, and should be resolved earlier in the code base. However, if that is not possible, use Invoke-Expression
like so
$newObj = Invoke-Expression $Obj
Further reading on Invoke-Expression

Cannot convert 'System.Object[]' to the type 'System.String'

I'm trying to establish a connection to a remote server that is not on Domain environment.
When running the Set-Item alone on PowerShell console without the script it works for some reason on the script it doesn't.
$passwd = convertto-securestring -String <Password> -AsPlainText -Force
$cred = New-Object -Typename System.Management.Automation.PSCredential -ArgumentList "developer", $passwd
$server = Read-Host -Prompt 'Input your server IP'
Set-Item wsman:\localhost\client\TrustedHosts -Value $server -Force
$session = new-pssession -computername $server -credential $cred
When I run the above code, I receive the message
Set-Item : Cannot convert 'System.Object[]' to the type
'System.String' required by the parameter. Specified method is not supported.
When you are passing the input here, it should be a string value and a single object, if there is a comma or something, then the $server no more remains as string.
To do a forceful conversion, you can convert the $server explicitly to String type by type casting:
[String]$server = Read-Host -Prompt 'Input your server IP'
Further,
I would like you to hardcode the server name in the Set-Item and see if you are getting the error. You will not get it mostly.
Set-Item wsman:\localhost\client\TrustedHosts -Value 'YourServerHostname' -Force
So, validate while taking the input that what kind of input you are getting.

Run SysPrep remotely through commands from Azure powershell

I want to run some commands to do Sysprep remotely through powershell.
So first I created a session using this:
$UserName = "IPAddress\username"
$Password = ConvertTo-SecureString "password" -AsPlainText -Force
$psCred = New-Object System.Management.Automation.PSCredential($UserName, $Password)
$s = New-PSSession -ComputerName IPAddress -Credential $psCred
Then assign the variables used in Sysprep:
$sysprep = 'C:\Windows\System32\Sysprep\Sysprep.exe'
$arg = '/generalize /oobe /shutdown /quiet'
$sysprep += " $arg"
And then finally run this:
Invoke-Command -Session $s -ScriptBlock {$sysprep}
When I run the last command, nothing happens actually.But in the script block, instead of $sysprep, if I give any other command like start/stop a service, it is giving some response. But SysPrep commands doesn't seem to work.Can anyone suggest what am I doing wrong.
You are trying to call for scriptblock object to run while $sysprep is a string. You would want to use Start-Process cmdlet for this. Like so:
$sysprep = 'C:\Windows\System32\Sysprep\Sysprep.exe'
$arg = '/generalize /oobe /shutdown /quiet'
Invoke-Command -Session $s -ScriptBlock {param($sysprep,$arg) Start-Process -FilePath $sysprep -ArgumentList $arg} -ArgumentList $sysprep,$arg

Build URL string

Trying to build a link with a variable and a string, but I always get a space in between them. How can I fix this?
The $sub is a SPWeb object from sharepoint.
Write-Host $sub.Url "/default.aspx"
result:
https://intra.mycompany/pages/sales /default.aspx
Put the $sub variable inside the string literal so that it is treated as one string:
Write-Host "$($sub.Url)/default.aspx"
Note that you will need to use a sub expression operator $(...) since you are accessing an attribute of $sub.
Another approach, depending on how complicated your string is, is to use the -f format operator:
Write-Host ("{0}/default.aspx" -f $sub.Url)
If you have many variables that you need to insert, it can make for cleaner and easier to read code.
Use the URL class' constructor to do the join, rather than using string manipulation. This will have the additional advantage of automatically take care of appending any slashes required.
function Join-Uri {
[CmdletBinding()]
param (
[Alias('Path','BaseUri')] #aliases so naming is consistent with Join-Path and .Net's constructor
[Parameter(Mandatory)]
[System.Uri]$Uri
,
[Alias('ChildPath')] #alias so naming is consistent with Join-Path
[Parameter(Mandatory,ValueFromPipeline)]
[string]$RelativeUri
)
process {
(New-Object -TypeName 'System.Uri' -ArgumentList $Uri,$RelativeUri)
#the above returns a URI object; if we only want the string:
#(New-Object -TypeName 'System.Uri' -ArgumentList $Uri,$RelativeUri).AbsoluteUri
}
}
$sub = new-object -TypeName PSObject -Property #{Url='http://demo'}
write-host 'Basic Demo' -ForegroundColor 'cyan'
write-host (Join-Uri $sub.Url '/default.aspx')
write-host (Join-Uri $sub.Url 'default.aspx') #NB: above we included the leading slash; here we don't; yet the output's consistent
#you can also easily do this en-masse; e.g.
write-host 'Extended Demo' -ForegroundColor 'cyan'
#('default.aspx','index.htm','helloWorld.aspx') | Join-Uri $sub.Url | select-object -ExpandProperty AbsoluteUri
Above I created a function to wrap up this functionality; but you could just as easily do something such as below:
[string]$url = (new-object -TypeName 'System.Uri' -ArgumentList ([System.Uri]'http://test'),'me').AbsoluteUri
Link to related documentation: https://msdn.microsoft.com/en-us/library/9hst1w91(v=vs.110).aspx

How to perform IISRESET with Powershell Script

Does anyone know how to perform IISRESET with a PowerShell script? I'm using the PowerGUI editor with PowerShell 1.0 installed on a Windows 2008 box.
You can do it with the Invoke-Command cmdlet:
invoke-command -scriptblock {iisreset}
UPDATE:
You can also simplify the command using the & call operator:
& {iisreset}
Having used & {iisreset} with occasional failure lead me to this:
Start-Process "iisreset.exe" -NoNewWindow -Wait
Now it waits for iisreset.exe to end gracefully.
This works well for me. In this application, I don't care about the return code:
Start-Process -FilePath C:\Windows\System32\iisreset.exe -ArgumentList /RESTART -RedirectStandardOutput .\iisreset.txt
Get-Content .\iisreset.txt | Write-Log -Level Info
The Write-Log cmdlet is a custom one I use for logging, but you could substitute something else.
I know that this is very old, but you can run any command line processes from Powershell's command line. So you would just need a script that calls IISReset with whatever switches you need.
Not sure what you are looking for exactly, but create a script with a body of "iisreset /noforce"
Here's an example: http://technet.microsoft.com/en-us/library/cc785436.aspx
IIS Stop or Start (tested)
WaitForExit and ExitCode work fine
[System.Reflection.Assembly]::LoadWithPartialName("System.Diagnostics").FullName
$procinfo = New-object System.Diagnostics.ProcessStartInfo
$procinfo.CreateNoWindow = $true
$procinfo.UseShellExecute = $false
$procinfo.RedirectStandardOutput = $true
$procinfo.RedirectStandardError = $true
$procinfo.FileName = "C:\Windows\System32\iisreset.exe"
$procinfo.Arguments = "/stop"
$proc = New-Object System.Diagnostics.Process
$proc.StartInfo = $procinfo
[void]$proc.Start()
$proc.WaitForExit()
$exited = $proc.ExitCode
$proc.Dispose()
Write-Host $exited
iisreset.exe supports computer names as a parameter. An example below show basic idea how to reset IIS on multiple servers:
$servers = #()
$servers += 'server1'
$servers += 'server2'
...
$servers += 'serverN'
Since iisreset.exe doesn't support multivalued parameters we have to wrap it in a loop:
$servers | %{ iisreset $_ /restart /noforce }
You may want to add simple monitoring:
$servers | %{ Write-Host "`n`n$_`n" -NoNewline ; iisreset $_ /restart /noforce /timeout:30 }
If you have many servers you may be interested in failures only:
$servers | %{ Write-Host "`n`n$_`n" -NoNewline ; iisreset $_ /restart /noforce /timeout:30 | Select-String "failed" }
Multiline version for better readability:
foreach ( $server in $servers ) {
Write-Host "`n`n$server`n" -NoNewline ;
iisreset $server /restart /noforce /timeout:30 | Select-String "failed"
}
I would strongly recommend testing your script with /status before implementing /reset action:
$servers | %{ iisreset $_ /status }
You may check stopped components with /status as well:
$servers | %{ Write-Host "`n`n$_`n" -NoNewline ; iisreset $_ /status | Select-String "Stopped" }
Reference
/restart is the default parameter. iisreset.exe users /restart in case no other actions params specified
/noforce will prevent iisreset.exe from running in case of an error.
/timeout - sometime you need to allow server more time to process request to avoid IIS stuck in Stopped state.
I found using the simple command below the easiest.
D:\PS\psexec \server_name iisreset

Resources