Commands executed in PowerShell with variables surrounded in quotes fail. Why? - powershell

I'm having a surprisingly difficult time embedding variables with quotes to an external command with PoSH. For example, this command
dfsradmin membership list /rgname:`"stuff I want`"
gives me the following expected result:
Failed:
Replication group with name stuff I want cannot be found.
This command, however
$group = "stuff I want"
dfsradmin membership list /rgname:`"$group`"
fails with this error:
Failed:
The subobject "/rgname:"stuff is not a valid subobject.
Is this a bug with Powershell or am I missing/misunderstanding something?

Yeah there are known issues in Powershell ( including v2.0) around this: http://connect.microsoft.com/PowerShell/feedback/details/376207/executing-commands-which-require-quotes-and-variables-is-practically-impossible
See if the alternatives discussed in the link above work for you. I cannot try it out as I don't have that executable.
Also echoargs.exe is a useful tool that you can use to see what arguments have been recevied from Powershell.

I found that defining
$quote = '"'
and then using /command$quote"test"$quote works as well

There's no need to add back ticks in front of quotes. Does this work for you?
$group = "stuff I want"
dfsradmin membership list /rgname:"$group"

So I was able to get around this by executing it in CMD.exe and doing string manipulations to get what I need.
$str = &cmd /c 'dfsradmin membership list /rgname:"blah blah"'
$str = &cmd /c "dfsradmin membership list /rgname:$blah" # with vars
Thanks for the help! I hope this has been resolved in Powershell 3.0.

I found a workaround which doesn't call cmd but uses Invoke-Expression instead. The command has to be put in a variable first:
$var = "string with spaces"
$command = "first part " + [char]96 + [char]34 + $var + [char]96 + [char]34 + " second part"
Invoke-Expression $command
Not that pretty but it works. You can replace [char]96 with '`' and [char]34 with '"' if you prefer. Easy to create a function which does it if you use it a lot.

All of the above did not work for me but based on Carlos idea, this is the solution that worked posted here
# get msdeploy exe
$MSDeploy = ${env:ProgramFiles}, ${env:ProgramFiles(x86)} |
ForEach-Object {Get-ChildItem -Path $_ -Filter 'MSDeploy.exe' -Recurse} |
Sort-Object -Property #{Expression={[version]$_.VersionInfo.FileVersion}} -Descending |
Select-Object -First 1 -ExpandProperty FullName
#build deploy command
$deplyCmd = """""$MSDeploy"" -verb:sync -dest:iisApp=""Default Web Site"" -enableRule:DoNotDeleteRule -source:iisApp=""$ExtraWebFilesFolder"""
#execute
&cmd /c $deplyCmd

I know this is old thread but just posting here in case my solution works for somebody as it worked for me.
This particular command (dfsradmin) expects natively seen quotes so I just enclosed value with quotes in single quotes thus passing quotes as well:
dfsradmin membership list /rgname:'"stuff I want"'
or if using through variable:
$group = '"stuff I want"'
dfsradmin membership list /rgname:$group

Related

Running a command with arguments assistance

I have a command which runs a program in silent mode, it uses an XML file for the data repository and a word template to create multiple word documents based on a filter xml file.
The command I use is:
"P:\ath to\executable" -Username:Admin -Password:Pa55w0rd -Datadefinition:"C:\Data.xml" -Datafilter:"C:\Filter.xml" -wordtemplate:"C:\Batch\Paul1.dotx" -Targetdocument:="C:\Batch\Paul1.pdf" -filetype:PDF -Log:"C:\Logs\error.log" -Usage:DOCGENSILENT
I need to run this as a PowerShell script which I have mostly managed:
set-executionpolicy unrestricted
$datadefinition = Get-Content "C:\Data file.xml"
$datafilter = Get-Content "C:\Filter for data file.xml"
$wordTemplate = Get-Content "C:\"C:\Template\Paul1.dotx"
$targetFolder = Get-Content "C:\"C:\Paul\Paul.pdf"
Stop-Job = "Executable path" -Username:Admin -Password:Pa55w0rd -Datadefinition:%dataDefinition% -Datafilter:%dataFilter% -wordtemplate:%wordTemplate% -Targetdocument:%targetFolder% -filetype:docx -Log:%logPath% -Usage:DOCGENSILENT
Stop-Job 1
set-executionpolicy restricted
Write-Host -NoNewLine "Press any key to continue..."
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
My issue is that the script starts the executable but then doesnt pass the Variables, can anyone guide me in the right direction to fix this?
Getting this working depends on the behavior of your executable. Some things I noticed:
Shouldn't this:
$wordTemplate = Get-Content "C:\"C:\Template\Paul1.dotx"
be this:
$wordTemplate = "C:\Template\Paul1.dotx"
Are you sure you need Get-Content? (Aside from that, the path and quoting in your sample are not correct.)
Shouldn't this:
$targetFolder = Get-Content "C:\"C:\Paul\Paul.pdf"
be this:
$targetDocument = "C:\Paul\Paul.pdf"
I doubt Get-Content is correct here, since presumably your output file doesn't exist yet? I also renamed the variable so it makes more sense in your command.
In fact, are you sure you need Get-Content for any of those? Aren't you specifying filenames, not the content of the files?
In PowerShell, variables are prefixed with $ rather than being surrounded by %.
Using Set-ExecutionPolicy within a script to enable scripts to run is pointless, because the script is already running. (That is, if execution policy prevented script execution, PowerShell wouldn't let you run the script in the first place.)
If my guesses regarding your variables are correct, I think your script should look something like this (note also that I specified a $logFile variable, which I didn't see in your script):
$datadefinition = "C:\Users\Administrator\data\Sample Model_146_object type(s).xml"
$datafilter = "C:\Users\Administrator\data\Sample Model_146_object type(s).xml"
$wordtemplate = "C:\Users\Administrator\Templates\Base object.docx"
$targetdocument = "C:\Users\Administrator\Result\sample test15"
$logfile = "C:\Users\Administrator\Logs\C4W Error.log"
& "C:\Program Files (x86)\Communicator4Word.exe" -Username:Admin -Password: -Datadefinition:$datadefinition -Datafilter:$datafilter -wordtemplate:$wordtemplate -Targetdocument:$targetdocument -filetype:docx -Log:$logfile -Usage:DOCGENSILENT
I don't know the behavior of Communicator4Word.exe when you use -Password: with no password after it. (Is that a syntax error, or should you just omit -Password: altogether?)

Escaping all password special characters in Powershell for Variable

I have the following scenario, running on Powershell v5:
A Powershell script pulls several bits of information from an API call to a 3rd party system in a bulk for-each loop and assigns them to Variables. Amongst the information that is pulled is Passwords (this is being done to get rid of said 3rd party system and to migrate it to something that doesn't allow you to retrieve passwords in plain text):
$userset = Invoke-WebRequest -Method Post -Uri "https://$Url/path/to/api.asmx" -Headers $Headers -Body $usercall
$xmluserset = [xml] $userset.Content
$userset2 = $xmluserset.Envelope.Body.UserSettingsResult.settingValues.string
$userpasstemp = $userset2[1].trimstart("password")
$userpass = $userpasstemp.trimstart("=")
These passwords are then used elsewhere in the Script.
For example, they are passed to a different API and need to be in a URL compatible format and so I run this:
$urlescapeduserpass = [uri]::EscapeDataString($userpass)
which works for that section of the Script
The problem is that these passwords can contain any of the special characters:
!"#$%&'()*+,-./:;<=>?#[]^_`{|}~
And when I call another part of the script, the special characters in the password string cause a failure and the script to exit. This occurs when using either the call command:
& .\application.exe --option1 $option1 --user1 $user --password1 $userpass
or when using invoke-expression
$command = "$path\application.exe --option1 $option1 --user1 $user --password1 $userpass"
Invoke-Expression $command
I've tried using Regex, using the -replace cmdlet:
$escapedpass = $userpass -replace ' !"#$%&()*+,-./:;<=>?#[\]^_`{|}~', '`$&'
But no luck, I know similar to the [uri]escapedatastring, there's a similar one for Regex, but there doesn't appear to be one native for Powershell. I'm sure there is either a [contenttype] that will have a native function to escape the special characters or some way to achieve the end-result.
Because PowerShell's handling of embedded " characters in argument passed to external programs is broken (as of PowerShell 7) - see this answer - you need to manually \-escape " characters embedded in your string:
$escapedpass = $userpass -replace , '"', '\"'
In the context of your command:
& .\application.exe --option1 $option1 --user1 $user --password1 ($userpass -replace , '"', '\"')
Let's create a Minimal, Reproducible Example with a round trip based on the answer How to escape special characters in PowerShell? from #mklement0 for this:
(Also take the comment in account that I just made on spaces)
Unfortunately, PowerShell creates an extra handicap as it requires to single quote the string otherwise it will interpret other characters along with the dollar sign ($). I have placed comments in the code where you might leave this out.
Password.ps1
Param (
[String]$Password
)
Write-Host $Password
Password check:
$Input = #'
!"'#$%&()*+,-./:;<=>?#[\]^_`{|}~
'#
Write-Host 'Input:' $Input
$Escape = $Input.Replace('"', '\"')
$Escape = $Escape.Replace("'", "''") # Only required for PowerShell
Write-Host 'Command:' PowerShell .\Password.ps1 "'$Escape'"
# Omit the inner single quotes if it doesn't concern PowerShell
# But note that your .\application.exe might have its own syntax
# to support spaces and special characters from the command line,
# like double quotes: """$Escape"""
$Output = & PowerShell .\Password.ps1 "'$Escape'"
Write-Host 'Output:' $Output
Write-Host 'Input and output are equal?' ($Input -eq $Output)
Results:
Input: !"'#$%&()*+,-./:;<=>?#[\]^_`{|}~
Command: PowerShell .\Password.ps1 "' !\"''#$%&()*+,-./:;<=>?#[\]^_`{|}~'"
Output: !"'#$%&()*+,-./:;<=>?#[\]^_`{|}~
Input and output are equal? True

Setting a computer room number with a PowerShell script

Thank you all for your replies. I have corrected the errors in my code, although they were just a cut and paste error. Here is an example with the error I get. If I do for example:
PS > Set-ADComputer "VM-WINDOWS7" -Location "NA/HQ/Building A"
PS > Get-ADComputer "VM-WINDOWS7" -Properties location |select-object location
It works and I get the result:
Location
--------
NA/HQ/Building A
But when I do:
PS > Set-ADComputer "VM-WINDOWS7" -RoomNumber "7"
Or
PS > Set-ADComputer "VM-WINDOWS7" -RoomNumber #{Replace="7"}
I get the error:
Set-ADComputer: Cannot find a parameter that matches the name "RoomNumber".
At line: 1 Character: 41
+ Set-ADComputer "VM-WINDOWS7" -RoomNumber <<<< #{Replace="7"}
+ CategoryInfo: InvalidArgument: (:) [Set-ADComputer], ParameterBindingException
+ FullyQualifiedErrorId : NamedParameterNotFound,Microsoft.ActiveDirectory.Management.Commands.SetADComputer
It’s like I said before, the parameter RoomNumber is not recognized, so I thought maybe it has another name, like for example ( Account name in AD = SAMAccountName in PowerShell ).
You have a couple of issues here for sure. You have mistyped the AD attribute roomNumber as rooomNumber. You also have a space where your hashtable is declared.
Much like ssaviers, I see some smart quotes as well. Those can usually come from a copy and paste but you need to be careful if those are in your code.
“ - smartquote
" - regular double quote
That being said the problematic part might just need to be updated:
Set-ADComputer "$HostName" -Replace #{roomNumber = "$RoomNumber"}
Found the answer:
Set-ADComputer doesn't have a -roomnumber parameter. But it can be modified using Add, Replace, Clear or Remove parameters.
The 'ROOMNUMBER' field is LDAP so it has a different syntax:
Set-ADComputer $HostName -replace #{roomNumber='$RoomNumber'} (that's what i used in the first place but i didn’t write it correctly)
Looks like a lot of the fields in AD especially for computers need to be done this way.
Anyway thank you all for your ideas and supports.
Cheers.

Appcmd Syntax for Arrays in Powershell

I'm trying to run the following to set the handler mappings for each of our websites using appcmd in powershell:
$websites = Get-ChildItem IIS:\Sites
foreach ($Site in $WebSites) {
C:\Windows\system32\inetsrv\appcmd set config "$site" /section:handlers -accessPolicy:"Read,Script,Execute"}
However I'm getting this error message for each site as it iterates through them:
ERROR ( message:Cannot find SITE object with identifier "Microsoft.IIs.PowerShell.Framework.ConfigurationElement". )
What am I doing wrong? I tried using a property of the $sites variable:
$site.name
but even that doesn't work. I'm at a loss. Thanks!
As you've encountered, "$site" doesn't expand to the name of the website, but to the type of object that $site refers to. "$site.name" is not way off, but in fact:
"$site" -eq "Microsoft.IIs.PowerShell.Framework.ConfigurationElement"
"$site.name" -eq "Microsoft.IIs.PowerShell.Framework.ConfigurationElement.name"
The PowerShell parser stops recognizing the variable name at ., and treats the rest (".name") as a string.
You can use the $() sub-expression operator to escape an entire statement:
"Name: $($site.name)"
You can do anything you like inside $() and nest them all you like:
"Random Site Name: $("$(Get-Random -Maximum ([int32]::MaxValue)){0}" -f $site.name)"

Getting the arguments of the last invoked command in powershell?

I want to be able to get the argument portion of the previous command. $^ seems to return just the command and not the args. Get-History -count 1 returns the last full command including the command and the args. I could just .Replace the first instance, but I am not sure if it is correct.
Scenario is that sometimes I want to do something like this. Let's assume that $* are the args to the last command:
dir \\share\files\myfile.exe
copy $* c:\windows\system32
Any ideas how to get the last args correctly?
UPDATE: finished my method for doing this.
function Get-LastArgs
{
$lastHistory = (Get-History -count 1)
$lastCommand = $lastHistory.CommandLine
$errors = [System.Management.Automation.PSParseError[]] #()
[System.Management.Automation.PsParser]::Tokenize($lastCommand, [ref] $errors) | ? {$_.type -eq "commandargument"} | select -last 1 -expand content
}
Now I can just do:
dir \\share\files\myfile.exe
copy (Get-LastArgs) c:\windows\system32
To reduce typing, I did
set-alias $* Get-LastArgs
so now I still have to do
copy ($*) c:\windows\system32
if anybody has any ideas for making this better please let me know.
For the last argument (not all!) in the interactive hosts like Console and ISE it is the automatic variable $$.
Help
man about_Automatic_Variables
gets
$$
Contains the last token in the last line received by the session.
Other hosts may or may not implement this feature (as well as the $^ variable).
There is no easy way to get the last args in this fashion without parsing the history item itself, and this is no trivial matter. The reason is that the "last arguments" may not be what you think they are after you take splatting, pipelines, nested subexpressions, named and unnammed arguments/parameters into the equasion. In powershell v2 there is a parser available for tokenizing commands and expressions, but I'm not sure you want to go that route.
ps> $psparser::Tokenize("dir foo", [ref]$null) | ? {
$_.type -eq "commandargument" } | select -last 1 -expand content
foo