Powershell Variable assigning issue - powershell

I am attemping to make the Destination from the Copy Item function be the $path and keep running into syntax error.
Function movefiles($dayhash){
foreach ($h in $dayhash.GetEnumerator() )
{
$path = "$formsfolderDaily Checklists\$today_($h.Value)"
Copy-Item $formsfolder$($h.Value) -Destination $formsfolder"Daily Checklists\"$today"_"$($h.Value)
editDate($path)
}
Desired outcome
Function movefiles($dayhash){
foreach ($h in $dayhash.GetEnumerator() )
{
$path = $formsfolder + "Daily Checklists\" + $today + "_" + ($h.Value)
Copy-Item $formsfolder$($h.Value) -Destination $path
editDate($path)
}

$path = "$formsfolderDaily Checklists\$today_($h.Value)"
This makes me think $FormsFolder is a path variable with a trailing backslash -- BUT THAT'S JUST A GUESS -- and one of the reasons Join-Path is so useful.
It's also hard to know what is a literal and what is part of a variblbe name when you start constructing complex expansion strings. I would recommend using the -f (Format operator) which nicely separates the literal and variable portions of your string. My best guess for the above would be:
$path = '{0}Daily Checklists\{1}_{2}' -f $formsfolder, $today, $h.Value
Your template string is on the the left-hand side of the operator, with zero-indexed placeholders in the format {0}, {1}, etc. The placeholders correspond to the variables/expressions/function calls found in the list on the right-hand side of the operator.

It sounds like you want to implement your solution using expandable (double-quoted) strings ("...").
To that end, you need to observe two fundamental rules:
In order to disambiguate variable names from subsequent characters, enclose their names in {...}, e.g. $[today} instead of just $today
Notably, _ is a legitimate character in a PowerShell variable name, so if a _ follows a variable reference {...} is needed too.
In order to embed expressions - such as $h.Value - inside "...", enclose them in $(...), the subexpression operator
You've done this in part in your question, but the first command is missing the $ before (.
For a complete overview of PowerShell's string-interpolation rules, see this answer.
Additionally:
You're using compound tokens composed of unquoted and quoted parts in order to form a single string argument, which is best avoided in PowerShell - see this answer.
Instead, use a single, "..."-enclosed string.
Therefore (the assumption is that the value of $formsfolder ends in \ (or /)):
Function movefiles($dayhash) {
foreach ($h in $dayhash.GetEnumerator() ) {
$path = "${formsfolder}Daily Checklists\$today_$($h.Value)"
Copy-Item "${formsfolder}$($h.Value)" -Destination "${formsfolder}Daily Checklists\${today}_$($h.Value)"
editDate $path
}

Related

powershell how to join special folder with file [duplicate]

I have the following code:
$srv_range = 29..30+40+50..52
$srv_range.GetType()
$NewVMTemplate = New-Object psobject
$NewVMTemplate | Add-Member -MemberType NoteProperty -Name Name -Value $null
$srv_range | % {
$pod= $_
$servers = #()
1..2 | % {
$server = $NewVMTemplate | Select-Object *
$server.Name = "pod" + "{0:D2}" -f $pod + "-srv" + $_
$servers += $server
}
ForEach ( $server in $servers) {
write-host $server.Name
}
}
output:
PowerCLI C:\ .\eraseme.ps1
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
pod29-srv1
pod29-srv2
pod30-srv1
pod30-srv2
pod40-srv1
pod40-srv2
pod50-srv1
pod50-srv2
pod51-srv1
pod51-srv2
pod52-srv1
pod52-srv2
I want to input the range from CLI, but I get the following output with this code
param(
[Parameter(Mandatory=$False)] $srv_range
)
#$srv_range = 29..30+40+50..52
$srv_range.GetType()
$NewVMTemplate = New-Object psobject
$NewVMTemplate | Add-Member -MemberType NoteProperty -Name Name -Value $null
$srv_range | % {
$pod= $_
$servers = #()
1..2 | % {
$server = $NewVMTemplate | Select-Object *
$server.Name = "pod" + "{0:D2}" -f $pod + "-srv" + $_
$servers += $server
}
ForEach ( $server in $servers) {
write-host $server.Name
}
}
PowerCLI C:\ .\eraseme.ps1 29..30+40+50..52
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True String System.Object
pod29..30+40+50..52-srv1
pod29..30+40+50..52-srv2
How can I input the range from CLI and get the same result as the first code?
Your problem is that argument 29..30+40+50..52 is treated as a string literal in your .\eraseme.ps1 29..30+40+50..52 call - it is not recognized as an expression.
To force recognition as an expression, enclose the argument in (...), the grouping operator:
.\eraseme.ps1 (29..30+40+50..52)
The same applies if you want to use output from (another) command as a command argument; e.g.:
# Pass the lines read from file paths.txt as an array to Get-ChildItem
# (Parameter -Path is implied in both commands).
Get-ChildItem (Get-Content paths.txt)
Two asides:
• $(...), the subexpression operator, is only ever needed in two cases: (a) to embed entire statement(s), notably loops and conditionals, in another statement, and (b) to embed an expression, command, or statement(s) inside "...", an expandable (interpolating) string. Just (...) is enough to embed a single command or expression in a statement (and even that isn't needed on the RHS of a variable assignment). While not likely, the unnecessary use of $(...) can have side effects - see this answer.
• You can make your script more robust by declaring your parameter with a more specific type, in which case an attempt to call it with a string would fail right away:
[Parameter(Mandatory=$False)] [int[]] $srv_range
(Other optimizations could be applied to your script as well.)
Optional background information
As for when an unquoted token is treated as an expression or nested command vs. an (expandable) string in argument mode (see also: about_Parsing):
(...), $(...), and #(...) by themselves or at the start of a token create a new parsing context, in which expressions or even nested commands can be used:
(...) is sufficient for a single expression or command. $(...) (the subexpression operator) can enclose multiple expressions / commands; so can #() (the array subexpression operator), and it additionally ensures that its output is always treated as an array.
Notably, the following expressions are not recognized without being enclosed in one of the above:
[...] (type literals) and access to their members, such as [Environment]::Version
.. (range expressions) such as 1..10
If, at the start of a token, (...), $(...), or #(...) are followed by additional characters, the first additional character is considered the start of a new, separate argument.
By contrast, if they're preceded by an unquoted literal or a variable-only reference, $(...) works like inside "..." (an expandable string), (...) starts a new argument that is an expression, and #(...) is taken as literal # with (...) again starting a new argument that is an expression.
A # followed by the name of a variable (e.g., #params) containing a collection or hashtable of parameter values initiates parameter splatting.
#{ ... } can be used to pass a hashtable literal (e.g., #{ key = 'value' }).
{ ... } creates a script block ([scriptblock]).
By themselves or at the start of a token, variable references, including member access (property access, method calls, indexing) can be used as-is:
Expressions such as $HOME, $PSVersionTable.PSVersion, $someArray[0], and $someString.ToUpper() are recognized, and returned as their inherent type.
Without member access, i.e., with a simple variable reference such as $HOME, subsequent characters are (potentially) considered part of the same argument that is then interpreted as an expandable string - see below.
With member access, the first of any additional characters is considered the start of a new argument (e.g., $foo.Length-more results in two arguments: the value of $foo.Length and string literal -more).
Everything else is treated as an expandable string, i.e., similar to the contents of a double-quoted string, except that metacharacters[1] still need escaping and certain tokens are interpreted as multiple arguments.
Expandable means that embedded simple variable references (e.g., $HOME\Desktop or $env:APPDATA\Test) are interpolated (replaced with their stringified values).
Note that this can result in a representation that differs from a given value's default output format as shown in the console, for instance (again, see this answer for more information).
Enclose a variable name in {...} to disambiguate it from subsequent characters, if necessary (e.g., ${HOME}).
To access a variable value's property or use an index or call a method or embed arbitrary commands, you must enclose the expression in $(...), e.g., v$($PSVersionTable.PSVersion)
Generally, it is safest to enclose tokens with embedded variable references / expressions in "...", because it avoids the following edge cases:
* $(...) at the start of an unquoted token is not interpreted as part of an expandable string, it is treated as a separate argument (e.g., Write-Output $('ab')c results in two arguments: the result of $('ab') and literal c).
* . at the start of a token immediately followed by a simple variable reference or subexpression results in separate arguments too.
(E.g., .$HOME results in two arguments: literal ., and the value of $HOME)
Note: Even though the result of the expansion is a string, it doesn't necessarily remain one: the final type is determined by the type of to the parameter of the command at hand to which the expanded value is bound.
Escaping / quoting:
PowerShell has many more metacharacters than cmd.exe, and a notable pitfall is that , must be escaped to be treated a literal, because , is PowerShell's array-construction operator.
To escape a single character, prefix it with ` (backtick).
To avoid the need for escaping metacharacters individually, enclose the value in "..." (double quotes) or '...' (single quotes):
Use double quotes if you want the string to be interpolated (expanded), i.e., if you want to be able to embed variable references and subexpressions.
Inside a double-quoted string, `-escape the following chars. to treat them as literals: ` " $
Use single quotes to treat the value as a literal.
Inside a single-quoted string, escape a ' as ''
Single- or double-quoting is usually the easiest way to escape spaces in a value.
Finally, note that --%, the so-called stop-parsing symbol (PSv3+), completely changes the interpretation of all remaining arguments: designed for use with legacy cmd.exe command lines, it stops interpreting the rest of the line except for expansion of cmd.exe-style %...% environment variables. See Get-Help about_Parsing
As for using quoted tokens:
'...' or "..." by themselves or at the start of a token:
These are parsed as as usual: as a literal ('...') or expandable ("...") string.
Any additional characters cause the first additional character to be considered the start of a new, separate argument.
'...' or "..." being preceded by an unquoted literal or variable-only reference:
They are evaluated as usual and the result (i.e., with quotes removed) is appended to what precedes them (evaluated to).
[1] The argument-mode metacharacters (characters with special syntactic meaning) are:
<space> ' " ` , ; ( ) { } | & < > # #.
Of these, < > # # are only special at the start of a token.
Examples
Write-Output 1..10 # STRING: -> '1..10'
Write-Output (1..10) # EXPRESSION: -> #(1, 2, ...)
# Write-Output $(1..10) would work too, but is only necessary if
# the enclosed expression comprises *multiple* statements.
Write-Output [Environment]::Version # STRING: -> '[Environment]::Ticks'
Write-Output ([Environment]::Version) # EXPRESSION: -> a [System.Version] instance.
Write-Output a,b # !! ARRAY #(1, 2), because "," is not escaped.
Write-Output a`,b #`# STRING 'ab'
Write-Output "a,b" # ditto
Write-Output 'a,b' # ditto
Write-Output $HOME\Desktop # EXPANDED string (e.g.) 'C:\Users\jdoe\Desktop'
Write-Output "$HOME\Desktop" # ditto
Write-Output '$HOME\Desktop' # LITERAL string '$HOME\Desktop'
Write-Output dir=$HOME # EXPANDED string (e.g.) 'dir=C:\Users\jdoe\Desktop'
Write-Output $PSVersionTable.PSVersion # a [System.Version] instance
Write-Output "$($PSVersionTable.PSVersion)/more" # a [string]; e.g., '5.1.14393.576/more'
Write-Output "v$($PSVersionTable.PSVersion)" # ditto; e.g., 'v5.1.14393.576'
# !!! These DO NOT WORK as intended.
Write-Output $($PSVersionTable.PSVersion)/more # $(...) at the *start*
Write-Output $PSVersionTable.PSVersion/more # $(...) missing
Write-Output "$PSVersionTable.PSVersion/more" # $(...) missing
Write-Output .$HOME # Specifically, .$ at the beginning is the problem; escaping . works

How to add a line to .txt file with special characters and variables in PowerShell

I have a PowerShell file e.g. C:\MyPowerShell.ps1 and I would like to have the following line there:
$myNewVariable = "Lukas"
Where string Lukas will be taken from variable $name. The variable $name will be declared before I run a command to do this action. I would like to use another PowerShell command to do that.
$name = "Lukas" <br>
Add-Content C:\MyPowerShell.txt ???
Please help me ;-)
Or use the -f Format operator:
$name = 'Lukas'
Add-Content -Path 'C:\MyPowerShell.txt' -Value ('$myNewVariable = "{0}"' -f $name)
Use an expandable (interpolating) string ("...") in which you individually `-escape $ characters in tokens you do not want to be expanded as variable references / subexpressions; similarly, escape embedded " characters as `" ("" would work too):
$name = 'Lucas'
Add-Content C:\MyPowerShell.txt -Value "`$myNewVariable = `"$name`""
Alternatively, use the -f operator, as shown in Theo's helpful answer.
This answer compares and contrasts these two approaches.

Powershell: How to remove space between $ and text[0]

Code:
$text=Get-Content -Path "E:\1.txt"
$text.GetType() | Format-Table -AutoSize
For($i=0; $i -le 5 ;$i++)
{
$var=Write-host '$'text[$i]
$var
}
Actual Result:
$ text[0]
$ text[1]
$ text[2]
$ text[3]
$ text[4]
$ text[5]
I need below Result:
$text[0]
$text[1]
$text[2]
$text[3]
$text[4]
$text[5]
If you must use the quotes, using -separator also works:
$text=Get-Content -Path "E:\1.txt"
$text.GetType() | Format-Table -AutoSize
For($i=0; $i -le 5 ;$i++)
{
$var=Write-host '$'text[$i] -Separator ''
$var
}
Your code fundamentally doesn't do what you intend it to do, due to the mistaken use of Write-Host:
# !! Doesn't capture anything in $var, prints directly to the screen.
$var=Write-host '$'text[$i]
$var # !! is effectively $null and produces no output.
See the bottom section for details.
Instead, what you want is an expandable string (aka interpolating string, "..."-enclosed), with selective `-escaping of the $ character you want to be treated verbatim:
$var= "`$text[$i]" # Expandable string; ` escapes the $ char., $i is expanded
$var
There are other ways to construct the desired string:
$var = '$text[{0}]' -f $i, using -f, the format operator.
$var = '$' + "text[$i]", using string concatenation with +
but the above approach is simplest in your case.
As for what you tried:
Write-Host is typically - and definitely in your case - the wrong tool to use, unless the intent is to write to the display only, bypassing the success output stream and with it the ability to send output to other commands, capture it in a variable, or redirect it to a file. To output a value, use it by itself; e.g., $value instead of Write-Host $value (or use Write-Output $value, though that is rarely needed); see this answer.
What you thought of as a single argument, '$'text[$i], was actually passed as two arguments, verbatim $ and expanded text[$i] (e.g., text[0]) and because Write-Host simply space-concatenates multiple arguments, a space was effectively inserted in the (for-display) output.
That '$'text[$i] becomes two arguments is a perhaps surprising PowerShell idiosyncrasy; unlike in POSIX-compatible shells such as bash, composing a single string argument from a mix of unquoted and (potentially differently) quoted parts only works if the argument starts with an unquoted substring or (mere) variable reference; for instance:
Write-Output foo'bar none' does pass a single argument (passes foobar none), whereas
Write-Output 'bar none'foo does not (passes bar none and foo)
See this answer for more information.

How to [int]add in a [string]regex replacement in powershell?

I'm renaming files with a regex replacement:
ls *dat | rename-item -newname {$_.name -replace '(.*)_([0-9]+)(.dat)','$1($2)$3'}
but I want also to add a constant int to [string]$2; something like :
'$1(#{int.parse($2)+3})($3)'
How should I proceed ?
PowerShell Core v6.1.0+ supports passing a script block as the replacement operand of the
-replace operator; the script block is invoked for each match found, and its output forms the replacement string, thereby enabling algorithmic replacements (rather than merely textual ones):
'foo_10.dat' -replace '(.*)_([0-9]+)(\.dat)', {
'{0}{1}{2}' -f $_.Groups[1].Value,
([int] $_.Groups[2].Value + 3),
$_.Groups[3].Value
}
The above yields:
foo13.dat
Note how 3 was added to 10 (and the underscore was removed):
$_ inside the script block is a [System.Text.RegularExpressions.Match] instance representing the results of the match; e.g., $_.Value represents the full match, whereas $_.Groups[1].Value represents what the 1st capture group matched.
The functionality is built on the [regex] .NET type's .Replace() method, which can also used directly (but less easily) in earlier PowerShell versions - see below.
In Windows PowerShell you have two options:
Use the -match operator first and then perform the transformation in a separate statement based on the match information reflected in the automatic $Matches variable, as suggested by kuujinbo:
$null = 'foo_10.dat' -match '(.*)_([0-9]+)(\.dat)' # match first
'{0}{1}{2}' -f $Matches.1, ([int] $Matches.2 + 3), $Matches.3 # build via $Matches
Use the .NET framework directly, with a script block passed as a delegate (callback function) to the above-mentioned [regex]::Replace() method, as suggested by Ansgar Wiechers:
([regex] '(.*)_([0-9]+)(\.dat)').Replace('foo_10.dat', {
param($match)
'{0}{1}{2}' -f $match.Groups[1].Value, ([int] $match.Groups[2].Value + 3), $match.Groups[3].Value
})
Note how a formal parameter - param($match) - must be declared here in order to access the match results, whereas the pure PowerShell solution at the top was able to use $_, as usual.

Powershell - using variables in replace

I was using .replace until I discovered it is case sensitive.
So I am rewritng a line of code to use -replace instead.
Here is what is working, but is case sensitive:
$SourcePath = 'c:\scripts\test
$folder = 'c:\testing\test'
$sourceFullPath = 'c:\scripts\test\FolderToTest'
$sourceFileRelativePath = $sourceFullPath.Replace($SourcePath, "")
$destFullFilePath = $folder + $sourceFileRelativePath
Write-output $destFullFilePath
c:\testing\test\FolderToTest
How would I convert this to use -replace or is there a way to use the .net .replace case-insensitive?
Note: This section of code will be in a function so they will not be static. I put in examples for this post but they could be any file path.
Thanks!!
Unlike the Replace method which takes strings, the replace operator takes a regular expression pattern. $SourcePath needs to be escaped as it contains backslashes which are special regex characters.
$sourceFileRelativePath = $sourceFullPath -replace [regex]::escape($SourcePath)
$destFullFilePath = Join-Path $folder $sourceFileRelativePath