Despite going over the documentation on single and double quotes using 'about_quoting_rules', I can't seem to get the output I'm looking for.
A little backstory:
I have an excel doc and I'm using powershell to create a foreach over every entry. This, in turn, is being used to generate a robocopy command to kick off a sync job.
The part of my script that's pertinent to this is below:
Foreach($script in $roboSource)
{
$StandardSwitches = "/copy:DAT /s /dcopy:DAT /V /L"
$log = "/log:`"$($logPath)$($logFileName)`"" #`
$FileSource = "$($script.a)"
$FileDestination = "$($script.a)"
$RoboArgs = "I:\" + $FileSource + " " + "Z:\" + $FileDestination + " " + $StandardSwitches + " " + $log
}
So, right now, $RoboArgs outputs this:
I:\XXXXX\XXXXX\X\XXXXXXX\XXXXX\XXXX Z:\XXXXX\XXXXX\X\XXXXXXX\XXXXX\XXXX /copy:DAT /s /dcopy:DAT /V /L /log:"C:\XXXXX\XXXXX\X\XXXXXXX\XXXXX\XXXX"
What I need $RoboArgs to output is this:
"I:\XXXXX\XXXXX\X\XXXXXXX\XXXXX\XXXX" "Z:\XXXXX\XXXXX\X\XXXXXXX\XXXXX\XXXX" /copy:DAT /s /dcopy:DAT /V /L /log:"C:\XXXXX\XXXXX\X\XXXXXXX\XXXXX\XXXX"
I've tried adding the backticks to the string, and encapsulating the string and variable together:
$RoboArgs = `""I:\" + $FileSource`"" + " " + "Z:\" + $FileDestination + " " + $StandardSwitches + " " + $log
But regardless of what I try, nothing is resulting in the output that I'm looking for. Any nudge in the right direction would be very helpful and appreciated!
In Powershell ` followed by quotes works, like '\' in other languages:
For example:
"`"toto`""
prints
"toto"
I suggest using PowerShell's string-formatting operator, -f:
$RoboArgs = '"I:\{0}" "Z:\{1}" {2} {3} {4}' -f
$FileSource, $FileDestination, $StandardSwitches, $log
Using '...' (single-quoting) as the delimiters allows you to embed " instances as-is.
{0} is a placeholder for the 1st RHS operand, {1} for the 2nd, and so on.
Try delimiting your string with single quotes. Any double qoutes inside will be preserved in the resulting string.
Example:
$stringWithQuotes = '"' + (pwd) + '"'
And you can also do it the other way around if you want single quotes in your string:
$stringWithQuotes = "'" + (pwd) + "'"
Related
I finally narrowed down the problem, but need help understanding why.
Invoking Powershell from a batch file, the following works to replace a string within a file:
Powershell -Command "$foobar=[IO.File]::ReadAllText("$Env:File") ;
$foobarred= $foobar -replace '(?<foo>.*Backup.).*.(?<bar>..Backup.)', '${foo}Enabled${bar}' ;
[IO.File]::WriteAllText("$Env:File", $foobarred); "
Now if I try this, the command fails:
Powershell -Command "$foobar=[IO.File]::ReadAllText("$Env:File") ;
$foobarred= $foobar -replace '(?<foo>.*$Env:StrStart.).*.(?<bar>..$Env:StrEnd.)', '${foo}$Env:StrVal${bar}' ;
[IO.File]::WriteAllText("$Env:File", $foobarred); "
If I use a variable passed in from Batch, it fails everytime. If I use PlainText in the command instead for the replacement value, it works just fine. Why does this happen?
My observations would be
you need to [regex]::Escape() arbitrary values when you build regular expressions dynamically.
PowerShell does not do any string interpolation in single-quoted strings, so things like '${foo}$Env:StrVal${bar}' will not work the way you want.
I'd use the following command:
(Get-Content "filename" -Raw) -replace (
'(.*' + [regex]::Escape("start string") + '.).*.(..' + [regex]::Escape("end string") + '.)'
),(
'$1' + "replacement string" + '$2'
) | Set-Content "filename"
called ad-hoc from a batch file as follows (compressed onto one line):
#echo off
setlocal
set "FILENAME=filename"
set "START=start string"
set "END=end string"
set "REPLACEMENT=replacement string"
set "PSCMD=(gc $Env:FILENAME -Raw) -replace ('(.*' + [regex]::Escape($Env:START) + '.).*.(..' + [regex]::Escape($Env:END) + '.)'),('$1' + $Env:REPLACEMENT + '$2') | sc $Env:FILENAME"
powershell -NoLogo -Command "&{%PSCMD%}"
But this is disproportionately hard to maintain.
I'd recommend writing a .ps1 file and passing named arguments, instead of juggling environment variables.
# MyReplace.ps1
param(
[string]$Filename,
[string]$Start,
[string]$End,
[string]$Replacement
)
$ErrorActionPreference = "Stop"
$content = Get-Content $Filename -Raw
$content = $content -replace ('(.*' + [regex]::Escape($Start) + '.).*.(..' + [regex]::Escape($End) + '.)'),('$1' + $Replacement + '$2')
$content | Set-Content $Filename
and in batch
powershell -NoLogo -File MyReplace.ps1 -Filename "filename" -Start "start string" -End "end string" -Replacement "replacement string"
That seems more manageable to me.
At the advice of #Gerhard, adding what I found into the fray as an answer, but did ultimately giving #Tomalak the credit for the better answer overall.
In order to accept variables into both the match pattern and the replace pattern, you have to concatenate the strings (similar to how it is done in Visual Basic).
Reference below:
I have split the command up into multiple lines for readability - if you use this, place it on one line.
Important - when using this, be sure to remove the line-breaks if you use it in a batch file. Also - be wary - I have had some circumstances where even using Set-Content can change the Encoding of the file. I much prefer the secondary solution offered down below.
Powershell -Command "$pattern= '(?<RangeStart>.*' + [regex]::Escape($Env:StrStart) + '.).*.(?<RangeEnd>..' + [regex]::Escape($Env:StrEnd) + '.)' ;
$repl= '${RangeStart}' + $Env:StrVal + '${RangeEnd}' ;
$fil2parse=(Get-Content $Env:FileTOParse) -replace $pattern, $repl | Set-Content $Env:FileTOParse; "
This solution works as well, but I have had much fewer issues with it changing Encoding.
Powershell -Command "$pattern= '(?<RangeStart>.*' + [regex]::Escape($Env:StrStart) + '.).*.(?<RangeEnd>..' + [regex]::Escape($Env:StrEnd) + '.)' ;
$repl= '${RangeStart}' + $Env:StrVal + '${RangeEnd}' ;
$fil2parse=[IO.File]::ReadAllText("$Env:FileTOParse") ;
$filParsed= $fil2parse -replace $pattern, $repl ;
[IO.File]::WriteAllText("$Env:FileTOParse", $filParsed); "
I'm using Powershell 5. I have a bunch of pdfs in my current working directory. I collect
and join their names as follows:
$(get-childitem *.pdf).name -join " "
The result is as follows:
pdf1.pdf pdf2.pdf pdf3.pdf
However, I need something like this:
"pdf1.pdf" "pdf2.pdf" "pdf3.pdf"
How do I get double quotes around each array element?
As #Cid says, this is one way to do it:
'"' + $($(get-childitem *.pdf).name -join '" "') + '"'
Suppose I have the following snippet:
$assoc = New-Object PSObject -Property #{
Id = 42
Name = "Slim Shady"
Owner = "Eminem"
}
Write-Host $assoc.Id + " - " + $assoc.Name + " - " + $assoc.Owner
I'd expect this snippet to show:
42 - Slim Shady - Eminem
But instead it shows:
42 + - + Slim Shady + - + Eminem
Which makes me think the + operator isn't appropriate for concatenating strings and variables.
How should you approach this with PowerShell?
Write-Host "$($assoc.Id) - $($assoc.Name) - $($assoc.Owner)"
See the Windows PowerShell Language Specification Version 3.0, p34, sub-expressions expansion.
There is a difference between single and double quotes. (I am using PowerShell 4).
You can do this (as Benjamin said):
$name = 'Slim Shady'
Write-Host 'My name is'$name
-> My name is Slim Shady
Or you can do this:
$name = 'Slim Shady'
Write-Host "My name is $name"
-> My name is Slim Shady
The single quotes are for literal, output the string exactly like this, please.
The double quotes are for when you want some pre-processing done (such as variables, special characters, etc.)
So:
$name = "Marshall Bruce Mathers III"
Write-Host "$name"
-> Marshall Bruce Mathers III
Whereas:
$name = "Marshall Bruce Mathers III"
Write-Host '$name'
-> $name
(I find How-to: Escape characters, Delimiters and Quotes good for reference).
You can also use -join
E.g.
$var = -join("Hello", " ", "world");
Would assign "Hello world" to $var.
So to output, in one line:
Write-Host (-join("Hello", " ", "world"))
One way is:
Write-Host "$($assoc.Id) - $($assoc.Name) - $($assoc.Owner)"
Another one is:
Write-Host ("{0} - {1} - {2}" -f $assoc.Id,$assoc.Name,$assoc.Owner )
Or just (but I don't like it ;) ):
Write-Host $assoc.Id " - " $assoc.Name " - " $assoc.Owner
Try wrapping whatever you want to print out in parentheses:
Write-Host ($assoc.Id + " - " + $assoc.Name + " - " + $assoc.Owner)
Your code is being interpreted as many parameters being passed to Write-Host. Wrapping it up inside parentheses will concatenate the values and then pass the resulting value as a single parameter.
Another option is:
$string = $assoc.ID
$string += " - "
$string += $assoc.Name
$string += " - "
$string += $assoc.Owner
Write-Host $string
The "best" method is probably the one C.B. suggested:
Write-Host "$($assoc.Id) - $($assoc.Name) - $($assoc.Owner)"
While expression:
"string1" + "string2" + "string3"
will concatenate the string, you need to put a $ in front of the parenthesis to make it evaluate as a single argument when passed to a PowerShell command. Example:
Write-Host $( "string1" + "string2" + "string3" )
As a bonus, if you want it to span multiple lines, then you need to use the awkward backtick syntax at the end of the line (without any spaces or characters to the right of the backtick).
Example:
Write-Host $(`
"The rain in " +`
"Spain falls mainly " +`
"in the plains" )`
-ForegroundColor Yellow
(Actually, I think PowerShell is currently implemented a little bit wrong by requiring unnecessary backticks between parentheses. If Microsoft would just follow Python or Tcl parenthesis rules of allowing you to put as many newlines as you want between the starting and ending parenthesis then they would solve most of the problems that people don't like about PowerShell related to line continuation, and concatenation of strings.
I've found that you can leave the backticks off sometimes on line continuations between parenthesis, but it's really flaky and unpredictable if it will work... It's better to just add the backticks.)
You need to place the expression in parentheses to stop them being treated as different parameters to the cmdlet:
Write-Host ($assoc.Id + " - " + $assoc.Name + " - " + $assoc.Owner)
(Current PowerShell version 5.1.17134.407)
This also works as of now:
$myVar = "Hello"
echo "${myVar} World"
Note: this only works with double quotes
Here is another way as an alternative:
Write-Host (" {0} - {1} - {2}" -f $assoc.Id, $assoc.Name, $assoc.Owner)
I just want to bring another way to do this using .NET String.Format:
$name = "Slim Shady"
Write-Host ([string]::Format("My name is {0}", $name))
These answers all seem very complicated. If you are using this in a PowerShell script you can simply do this:
$name = 'Slim Shady'
Write-Host 'My name is'$name
It will output
My name is Slim Shady
Note how a space is put between the words for you
I seem to struggle with this (and many other unintuitive things) every time I use PowerShell after time away from it, so I now opt for:
[string]::Concat("There are ", $count, " items in the list")
Concatenate strings just like in the DOS days. This is a big deal for logging so here you go:
$strDate = Get-Date
$strday = "$($strDate.Year)$($strDate.Month)$($strDate.Day)"
Write-Output "$($strDate.Year)$($strDate.Month)$($strDate.Day)"
Write-Output $strday
From What To Do / Not to Do in PowerShell: Part 1:
$id = $assoc.Id
$name = $assoc.Name
$owner = $assoc.owner
"$id - $name - $owner"
Write-Host can concatenate like this too:
Write-Host $assoc.Id" - "$assoc.Name" - "$assoc.Owner
This is the simplest way, IMHO.
If you're concatenating strings to build file paths, use the Join-Path command:
Join-Path C:\temp "MyNewFolder"
It'll automatically add the appropriate trailing / leading slashes for you, which makes things a lot easier.
$assoc = #{
Id = 34
FirstName = "John"
LastName = "Doe"
Owner = "Wife"
}
$assocId = $assoc.Id
$assocFN = $assoc.FirstName
$assocLN = $assoc.LastName
$assocName = $assocFN, $assocLN -Join " "
$assocOwner = $assoc.Owner
$assocJoin = $assocId, $assocName, $assocOwner -join " - "
$assocJoin
#Output = 34 - John Doe - Wife
As noted elsewhere, you can use join.
If you are using commands as inputs (as I was), use the following syntax:
-join($(Command1), "," , $(Command2))
This would result in the two outputs separated by a comma.
See https://stackoverflow.com/a/34720515/11012871 for related comment
Personally I prefer this style:
[string]::Join(' - ', 42, 'Slim Shady', 'Eminem')
or based on any object with even different attribute types:
$assoc = [psCustomObject][ordered]#{
Id = 42
Name = 'Slim Shady'
Owner = 'Eminem'
}
[string]::Join(' - ',$assoc.psObject.Properties.value)
which gives you this result:
42 - Slim Shady - Eminem
Just for the fun. You can also access the values of the PSObject directly like below:
$assoc.psobject.Properties.value -join " - "
But if you do not specify that the object should be ordered, PowerShell will display the values in a random order. So you should add the flag [ordered]:
$assoc = [pscustomobject] [ordered] #{
Id = 42
Name = "Slim Shady"
Owner = "Eminem"
}
You can also get access to C#/.NET methods, and the following also works:
$string1 = "Slim Shady, "
$string2 = "The real slim shady"
$concatString = [System.String]::Concat($string1, $string2)
Output:
Slim Shady, The real slim shady
$timeout = new-timespan -Minutes 1
$sw = [diagnostics.stopwatch]::StartNew()
$path = "d:\powershell\test.csv"
"Processor Load, Available Memory(MB), Max Memory(Bytes)" >> $path
while ($sw.elapsed -lt $timeout)
{
$a = gwmi -query "Select * from win32_processor"
$b = gwmi -query "Select * from win32_perfrawdata_perfOS_memory"
$c = gwmi -query "Select * from win32_physicalmemory"
$date = Get-Date -format s
$a.loadpercentage + "," + $b.availableMbytes + "," + $c.capacity >> $path
start-sleep -seconds 5
}
So I'm just looking to get a minute long snapshot of what's going on. I'm not just opening this in perfmon for reasons. Basically I'd expect to get a comma-delimited output in the CSV file mentioned. It works for a single variable, but when I try to add a second variable, or text I get the following error.
Cannot convert value ", test" to type "System.Int32". Error: "Input string was not in a
correct format."
At D:\powershell\VDIPerfMon.ps1:14 char:21
+ $a.loadpercentage + <<<< ", test" >> $path
+ CategoryInfo : NotSpecified: (:) [], RuntimeException
+ FullyQualifiedErrorId : RuntimeException
How can I fix this problem?
When you use the + operator PowerShell looks on the left hand side to determine the resulting type of the expression. It is seeing an int on the left of the + and a string (that can't be converted to an int) on the right. Try it this way:
"$($a.loadpercentage), $($b.availableMbytes), $($c.capacity)" >> $path
Also where you write your headers, you might not want to append i.e. in order to overwrite old attempts:
"Processor Load, Available Memory(MB), Max Memory(Bytes)" > $path
The error is because $a.loadpercentage is an int. You are then trying to add an int and a string.
One workaround is to explicitly call .ToString()
$a.loadpercentage.ToString() + "," + $b.availableMbytes.ToString() + "," + $c.capacity.ToString() >> $path
Another way is the PowerShell array join operator. It is quick easy, and types do not matter:
($a.loadpercentage, $b.availableMbytes, $c.capacity) -join "," |
Add-Content $path
Yet another way is with a string formatter. This will easily let you control the precision and display of each value:
'{0},{1},{2}' -f $a.loadpercentage, $b.availableMbytes, $c.capacity
Suppose I have the following snippet:
$assoc = New-Object PSObject -Property #{
Id = 42
Name = "Slim Shady"
Owner = "Eminem"
}
Write-Host $assoc.Id + " - " + $assoc.Name + " - " + $assoc.Owner
I'd expect this snippet to show:
42 - Slim Shady - Eminem
But instead it shows:
42 + - + Slim Shady + - + Eminem
Which makes me think the + operator isn't appropriate for concatenating strings and variables.
How should you approach this with PowerShell?
Write-Host "$($assoc.Id) - $($assoc.Name) - $($assoc.Owner)"
See the Windows PowerShell Language Specification Version 3.0, p34, sub-expressions expansion.
There is a difference between single and double quotes. (I am using PowerShell 4).
You can do this (as Benjamin said):
$name = 'Slim Shady'
Write-Host 'My name is'$name
-> My name is Slim Shady
Or you can do this:
$name = 'Slim Shady'
Write-Host "My name is $name"
-> My name is Slim Shady
The single quotes are for literal, output the string exactly like this, please.
The double quotes are for when you want some pre-processing done (such as variables, special characters, etc.)
So:
$name = "Marshall Bruce Mathers III"
Write-Host "$name"
-> Marshall Bruce Mathers III
Whereas:
$name = "Marshall Bruce Mathers III"
Write-Host '$name'
-> $name
(I find How-to: Escape characters, Delimiters and Quotes good for reference).
You can also use -join
E.g.
$var = -join("Hello", " ", "world");
Would assign "Hello world" to $var.
So to output, in one line:
Write-Host (-join("Hello", " ", "world"))
One way is:
Write-Host "$($assoc.Id) - $($assoc.Name) - $($assoc.Owner)"
Another one is:
Write-Host ("{0} - {1} - {2}" -f $assoc.Id,$assoc.Name,$assoc.Owner )
Or just (but I don't like it ;) ):
Write-Host $assoc.Id " - " $assoc.Name " - " $assoc.Owner
Try wrapping whatever you want to print out in parentheses:
Write-Host ($assoc.Id + " - " + $assoc.Name + " - " + $assoc.Owner)
Your code is being interpreted as many parameters being passed to Write-Host. Wrapping it up inside parentheses will concatenate the values and then pass the resulting value as a single parameter.
Another option is:
$string = $assoc.ID
$string += " - "
$string += $assoc.Name
$string += " - "
$string += $assoc.Owner
Write-Host $string
The "best" method is probably the one C.B. suggested:
Write-Host "$($assoc.Id) - $($assoc.Name) - $($assoc.Owner)"
While expression:
"string1" + "string2" + "string3"
will concatenate the string, you need to put a $ in front of the parenthesis to make it evaluate as a single argument when passed to a PowerShell command. Example:
Write-Host $( "string1" + "string2" + "string3" )
As a bonus, if you want it to span multiple lines, then you need to use the awkward backtick syntax at the end of the line (without any spaces or characters to the right of the backtick).
Example:
Write-Host $(`
"The rain in " +`
"Spain falls mainly " +`
"in the plains" )`
-ForegroundColor Yellow
(Actually, I think PowerShell is currently implemented a little bit wrong by requiring unnecessary backticks between parentheses. If Microsoft would just follow Python or Tcl parenthesis rules of allowing you to put as many newlines as you want between the starting and ending parenthesis then they would solve most of the problems that people don't like about PowerShell related to line continuation, and concatenation of strings.
I've found that you can leave the backticks off sometimes on line continuations between parenthesis, but it's really flaky and unpredictable if it will work... It's better to just add the backticks.)
You need to place the expression in parentheses to stop them being treated as different parameters to the cmdlet:
Write-Host ($assoc.Id + " - " + $assoc.Name + " - " + $assoc.Owner)
(Current PowerShell version 5.1.17134.407)
This also works as of now:
$myVar = "Hello"
echo "${myVar} World"
Note: this only works with double quotes
Here is another way as an alternative:
Write-Host (" {0} - {1} - {2}" -f $assoc.Id, $assoc.Name, $assoc.Owner)
I just want to bring another way to do this using .NET String.Format:
$name = "Slim Shady"
Write-Host ([string]::Format("My name is {0}", $name))
These answers all seem very complicated. If you are using this in a PowerShell script you can simply do this:
$name = 'Slim Shady'
Write-Host 'My name is'$name
It will output
My name is Slim Shady
Note how a space is put between the words for you
I seem to struggle with this (and many other unintuitive things) every time I use PowerShell after time away from it, so I now opt for:
[string]::Concat("There are ", $count, " items in the list")
Concatenate strings just like in the DOS days. This is a big deal for logging so here you go:
$strDate = Get-Date
$strday = "$($strDate.Year)$($strDate.Month)$($strDate.Day)"
Write-Output "$($strDate.Year)$($strDate.Month)$($strDate.Day)"
Write-Output $strday
From What To Do / Not to Do in PowerShell: Part 1:
$id = $assoc.Id
$name = $assoc.Name
$owner = $assoc.owner
"$id - $name - $owner"
Write-Host can concatenate like this too:
Write-Host $assoc.Id" - "$assoc.Name" - "$assoc.Owner
This is the simplest way, IMHO.
If you're concatenating strings to build file paths, use the Join-Path command:
Join-Path C:\temp "MyNewFolder"
It'll automatically add the appropriate trailing / leading slashes for you, which makes things a lot easier.
$assoc = #{
Id = 34
FirstName = "John"
LastName = "Doe"
Owner = "Wife"
}
$assocId = $assoc.Id
$assocFN = $assoc.FirstName
$assocLN = $assoc.LastName
$assocName = $assocFN, $assocLN -Join " "
$assocOwner = $assoc.Owner
$assocJoin = $assocId, $assocName, $assocOwner -join " - "
$assocJoin
#Output = 34 - John Doe - Wife
As noted elsewhere, you can use join.
If you are using commands as inputs (as I was), use the following syntax:
-join($(Command1), "," , $(Command2))
This would result in the two outputs separated by a comma.
See https://stackoverflow.com/a/34720515/11012871 for related comment
Personally I prefer this style:
[string]::Join(' - ', 42, 'Slim Shady', 'Eminem')
or based on any object with even different attribute types:
$assoc = [psCustomObject][ordered]#{
Id = 42
Name = 'Slim Shady'
Owner = 'Eminem'
}
[string]::Join(' - ',$assoc.psObject.Properties.value)
which gives you this result:
42 - Slim Shady - Eminem
Just for the fun. You can also access the values of the PSObject directly like below:
$assoc.psobject.Properties.value -join " - "
But if you do not specify that the object should be ordered, PowerShell will display the values in a random order. So you should add the flag [ordered]:
$assoc = [pscustomobject] [ordered] #{
Id = 42
Name = "Slim Shady"
Owner = "Eminem"
}
You can also get access to C#/.NET methods, and the following also works:
$string1 = "Slim Shady, "
$string2 = "The real slim shady"
$concatString = [System.String]::Concat($string1, $string2)
Output:
Slim Shady, The real slim shady