Powershell Using Backtick for new lines - weirdness - powershell

What exaxctly is happening in example 1? How is this parsed?
# doesnt split on ,
[String]::Join(",",("aaaaa,aaaaa,aaaaa,aaaaa,aaaaa,aaaaa," + `
"aaaaa,aaaaa,aaaaa,aaaaa,aaaaa,aaaaa,aaaaa,aaaaa,aaaaa,aaaaa".Split(',') `
| foreach { ('"' + $_ + '"') }))
# adding ( ) does work
[String]::Join(",",(("aaaaa,aaaaa,aaaaa,aaaaa,aaaaa,aaaaa," + `
"aaaaa,aaaaa,aaaaa,aaaaa,aaaaa,aaaaa,aaaaa,aaaaa,aaaaa,aaaaa").Split(',') `
| foreach { ('"' + $_ + '"') }))

In you first example you may remove the backtick, because Powershell knows that the string will continue (there is a + sign).
What posh does
takes string "aaaa,aaaa..." (1) from first
evaluates the expression with split - it returns array of strings (from "aaaa,...aaaa".Split(','))
converts the array of strings to string, which returns again string "aaaa,...aaaa"
adds results from 1. and 3.
Note: when posh converts array to string, it uses $ofs variable. You will see it better in action when you will try this code:
$ofs = "|"
[String]::Join(",", ("aaaaa,aaaaa" + "bbbb,bbbb,bbbb".Split(',') | foreach { ('"' + $_ + '"') }))

Your first example only has the Split method applied to the second string of a's. The parentheses are necessary for order of operations. The Split method is performed before the concatenation in your first example.

Related

Powershell replace last two occurrences of a '/' in file path with '.'

I have a filepath, and I'm trying to remove the last two occurrences of the / character into . and also completely remove the '{}' via Powershell to then turn that into a variable.
So, turn this:
xxx-xxx-xx\xxxxxxx\x\{xxxx-xxxxx-xxxx}\xxxxx\xxxxx
Into this:
xxx-xxx-xx\xxxxxxx\x\xxxx-xxxxx-xxxx.xxxxx.xxxxx
I've tried to get this working with the replace cmdlet, but this seems to focus more on replacing all occurrences or the first/last occurrence, which isn't my issue. Any guidance would be appreciated!
Edit:
So, I have an excel file and i'm creating a powershell script that uses a for each loop over every row, which amounts to thousands of entries. For each of those entries, I want to create a secondary variable that will take the full path, and save that path minus the last two slashes. Here's the portion of the script that i'm working on:
Foreach($script in $roboSource)
{
$logFileName = "$($script.a).txt".Replace('(?<=^[^\]+-[^\]+)-','.')
}
$script.a will output thousands of entries in this format:
xxx-xxx-xx\xxxxxxx\x{xxxx-xxxxx-xxxx}\xxxxx\xxxxx
Which is expected.
I want $logFileName to output this:
xxx-xxx-xx\xxxxxxx\x\xxxx-xxxxx-xxxx.xxxxx.xxxxx
I'm just starting to understand regex, and I believe the capture group between the parenthesis should be catching at least one of the '\', but testing attempts show no changes after adding the replace+regex.
Please let me know if I can provide more info.
Thanks!
You can do this in two fairly simply -replace operations:
Remove { and }
Replace the last two \:
$str = 'xxx-xxx-xx\xxxxxxx\x\{xxxx-xxxxx-xxxx}\xxxxx\xxxxx'
$str -replace '[{}]' -replace '\\([^\\]*)\\([^\\]*)$','.$1.$2'
The second pattern matches:
\\ # 1 literal '\'
( # open first capture group
[^\\]* # 0 or more non-'\' characters
) # close first capture group
\\ # 1 literal '\'
( # open second capture group
[^\\]* # 0 or more non-'\' characters
) # close second capture group
$ # end of string
Which we replace with the first and second capture group values, but with . before, instead of \: .$1.$2
If you're using PowerShell Core version 6.1 or newer, you can also take advantage of right-to-left -split:
($str -replace '[{}]' -split '\\',-3) -join '.'
-split '\\',-3 has the same effect as -split '\\',3, but splitting from the right rather than the left.
A 2-step approach is simplest in this case:
# Input string.
$str = 'xxx-xxx-xx\xxxxxxx\x\{xxxx-xxxxx-xxxx}\xxxxx\xxxxx'
# Get everything before the "{"
$prefix = $str -replace '\{.+'
# Get everything starting with the "{", remove "{ and "}",
# and replace "\" with "."
$suffix = $str.Substring($prefix.Length) -replace '[{}]' -replace '\\', '.'
# Output the combined result (or assign to $logFileName)
$prefix + $suffix
If you wanted to do it with a single -replace operation (with nesting), things get more complicated:
Note: This solution requires PowerShell Core (v6.1+)
$str -replace '(.+)\{(.+)\}(.+)',
{ $_.Groups[1].Value + $_.Groups[2].Value + ($_.Groups[3].Value -replace '\\', '.') }
Also see the elegant PS-Core-only -split based solution with a negative index (to split only a fixed number of tokens off the end) in Mathias R. Jessen's helpful answer.
try this
$str='xxx-xxx-xx\xxxxxxx\x\{xxxx-xxxxx-xxxx}\xxxxx\xxxxx'
#remove bracket and split for get array
$Array=$str -replace '[{}]' -split '\\'
#take all element except 2 last elements, and concat after last elems
"{0}.{1}.{2}" -f ($Array[0..($Array.Length -3)] -join '\'), $Array[-2], $Array[-1]

foreach pipe in multipleline string

Here is my code:
$collection1 = "1","2","3","4"
"
collection1:
$($collection1 | % {$_})
"
The output is:
collection1:
1 2 3 4
However, I'm expecting:
collection1:
1
2
3
4
So I changed my code to:
"
$($collection1 | % {$_ + "`n"})
"
Now, it shows:
collection1:
1
2
3
4
Why there is always an extra space in the front of each line?
Any way to remove them?
I tried to use Trim(), [String]::Format(), and few other ways, none is working as expected.
I think I found the answer... For anyone's interest...
$collection1 = "1","2","3","4"
$body = [System.Text.StringBuilder]::new()
foreach($item in $collection1)
{
[void]$body.Append($item+"`n")
}
"
collection1:
$body
"
$collection1 | % {$_} takes the array $collection1 and outputs each of its elements, which gives you the exact same thing you strated with: an array of the elements of $collection1. Using a subexpression to put that array into a string auto-converts the array to a string, so that it can be inserted into the outer string. In PowerShell this array-to-string conversion is done by joining the array elements with the $OFS character (Output Field Separator, by default a single space), which for your example produces the string "1 2 3 4".
The simplest way of getting the result you expect is to convert the array to a formatted string yourself:
"
collection1:
$($collection1 | Out-String)
"
The Out-String cmdlet converts array input to a single string by joining all input strings with newlines. Alternatively join the array yourself:
"
collection1:
$($collection1 -join "`n")
"
You could also (temporarily) modify $OFS and use the variable directly:
$OFS_bak = $OFS
$OFS = "`n"
"
collection1:
$collection1
"
$OFS = $OFS_bak
although I wouldn't normally recommend that.
As a side note: when working with a format/template string it's usually a good idea to use here-strings (so that don't need to escpae quotes inside the strings) and the format operator (-f):
#"
collection1:
{0}
"# -f ($collection1 | Out-String)

String concatenation with newline not working as expected

I want to add a line break without adding a whole 'nother line to do so, here be my code:
"-- MANIFEST COUNT -- " >> "C:\psTest\test1.txt"
$manCount = (Get-ChildItem -filter "*manifest.csv").Count
$manCount + " `n" >> "C:\psTest\test1.txt"
I thought that + " `n" would tack a line break onto the count, but it's not doing anything. I tried also + "`r`n" (I found this suggestion elsewhere on SO) but to no avail.
Let me complement your own solution with an explanation:
Because $manCount, the LHS, is of type [int],
$manCount + " `n"
is effectively the same as:
$manCount + [int] " `n".Trim()
or:
$manCount + [int] ""
which is effectively the same as:
$manCount + 0
and therefore a no-op.
In PowerShell, the type of the LHS of an expression typically[1]
determines what type the RHS will be coerced to, if necessary.
Therefore, by casting $manCount to [string], + then performs string concatenation, as you intended.
As Matt points out in a comment on your answer, you can also use string interpolation:
"$manCount `n"
[1]There are exceptions; e.g., '3' - '1' yields [int] 2; i.e., PowerShell treats both operands as numbers, because operator - has no meaning in a string context.
The integer needed to be cast as a string in order for the concatenation to take:
"-- MANIFEST COUNT -- " >> "C:\psTest\test1.txt"
$manCount = (Get-ChildItem -filter "*manifest.csv").Count
[string]$manCount + "`r`n" >> "C:\psTest\test1.txt"

Is there a better way to convert all control characters to entities in PowerShell 5?

Context: Azure, Windows Server 2012, PowerShell 5
I've got the following code to convert all control characters (ascii and unicode whitespace other than \x20 itself) to their ampersand-hash equivalents.
function ConvertTo-AmpersandHash {
param ([Parameter(Mandatory)][String]$Value)
# there's got to be a better way of doing this.
$AMPERHASH = '&#'
$SEMICOLON = ';'
for ($i = 0x0; $i -lt 0x20; $i++) {
$value = $value -replace [char]$i,($AMPERHASH + $i + $SEMICOLON)
}
for ($i = 0x7f; $i -le 0xa0; $i++) {
$value = $value -replace [char]$i,($AMPERHASH + $i + $SEMICOLON)
}
return $Value
}
As can be seen by the embedded comment, I'm sure there's a better way to do this. As it stands, one does some 65 iterations for each incoming string. Would regular expressions work better/faster?
LATER
-replace '([\x00-\x1f\x7f-\xa0])',('&#' + [byte][char]$1 + ';')
looks promising but the $1 is evaluating to zero all the time, giving me  all the time.
LATER STILL
Thinking that -replace couldn't internally iterate, I came up with
$t = [char]0 + [char]1 + [char]2 + [char]3 + [char]4 + [char]5 + [char]6
$r = '([\x00-\x1f\x7f-\xa0])'
while ($t -match [regex]$r) {
$t = $t -replace [regex]$r, ('&#' + [byte][char]$1 + ';')
}
echo $t
However out of that I still get

FINALLY
function ConvertTo-AmpersandHash {
param ([Parameter(Mandatory)][String]$Value)
$AMPERHASH = '&#'
$SEMICOLON = ';'
$patt = '([\x00-\x1f\x7f-\xa0]{1})'
while ($Value -match [regex]$patt) {
$Value = $Value -replace $Matches[0], ($AMPERHASH + [byte][char]$Matches[0] + $SEMICOLON)
}
return $Value
}
That works better. Faster too. Any advances on that?
Kory Gill's answer with the library call is surely a better approach, but to address your regex question, you can't evaluate code in the replacement with the -replace operator.
To do that, you need to use the .Net regex replace method, and pass it a scriptblock to evaluate the replacement, which takes a parameter of the match. e.g.
PS C:\> [regex]::Replace([string][char]2,
'([\x00-\x20\x7f-\xa0])',
{param([string]$m) '&#' + [byte][char]$m + ';'})

Your question is a little unclear to me, and could be a duplicate of What is the best way to escape HTML-specific characters in a string (PowerShell)?.
It would be nice if you explicitly stated the exact string you have and what you want it to converted to. One has to read the code to try to guess.
I am guessing one or more of these functions will do what you want:
$a = "http://foo.org/bar?baz & also <value> conversion"
"a"
$a
$b = [uri]::EscapeDataString($a)
"b"
$b
$c = [uri]::UnescapeDataString($b)
"c"
$c
Add-Type -AssemblyName System.Web
$d = [System.Web.HttpUtility]::HtmlEncode($a)
"d"
$d
$e = [System.Web.HttpUtility]::HtmlDecode($d)
"e"
$e
Gives:
a
http://foo.org/bar?baz & also <value> conversion
b
http%3A%2F%2Ffoo.org%2Fbar%3Fbaz%20%26%20also%20%3Cvalue%3E%20conversion
c
http://foo.org/bar?baz & also <value> conversion
d
http://foo.org/bar?baz & also <value> conversion
e
http://foo.org/bar?baz & also <value> conversion
I have one small function which helps me replacing as per my requirement:
$SpecChars are all the characters that are going to be replaced with nothing
Function Convert-ToFriendlyName
{param ($Text)
# Unwanted characters (includes spaces and '-') converted to a regex:
$SpecChars = '\', ' ','\\'
$remspecchars = [string]::join('|', ($SpecChars | % {[regex]::escape($_)}))
# Convert the text given to correct naming format (Uppercase)
$name = (Get-Culture).textinfo.totitlecase(“$Text”.tolower())
# Remove unwanted characters
$name = $name -replace $remspecchars, ""
$name
}
Example: Convert-ToFriendlyName "My\Name\isRana\Dip " will result me "MyNameIsranaDip".
Hope it helps you.

How to enumerate a hash in powerhshell where the values are arrays of strings

I have a hashtable defined in powershell as below:
$jobs = #{
"Test1"=[Array](
[Array]("\\server1\file1.txt", "\\server2\file1.txt")
);
"Test2"=[Array](
[Array]("\\sever1\file2.txt", "\\server2\file2.txt"),
[Array]("\\server1\file3.txt", "\\server2\file3.txt")
);
}
I am trying to enumerate over this collection and call a function on each pair. Test1 has 1 file comparison and Test2 has two file comparisons.
$jobs.GetEnumerator() | Sort-Object Key | foreach {
LogWrite($_.Key + ": comparing files")
$_.value | foreach {
LogWrite("Comparing files '" + $_[0] + "' and '" + $_[1] + "'")
#$r = CompareFiles($_[0], $_[1])
#LogWrite("Result : " + $r)
}
LogWrite($_.Key + ": comparison successful")
LogWrite("")
}
The output I am getting is:
Test1: comparing files
Comparing files '\' and '\'
Comparing files '\' and '\'
Test1: comparison successful
Test2: comparing files
Comparing files '\\server1\file2.txt' and '\\server2\file2.txt'
Comparing files '\\server1\file3.txt' and '\\server2\file3.txt'
Test2: comparison successful
Powershell (something) seems to be creating equal sized arrays. Can anyone suggest a better data structure or solution?
Referencing Avoiding Agnostic Jagged Array Flattening in Powershell, it would appear if you add a comma in the single-element array, it will stop it from being flattened:
$jobs = #{
"Test1"=[Array](,
[Array]("\\server1\file1.txt", "\\server2\file1.txt")
);
"Test2"=[Array](
[Array]("\\sever1\file2.txt", "\\server2\file2.txt"),
[Array]("\\server1\file3.txt", "\\server2\file3.txt")
);
}
There must be something odd about the way arrays are handled in Powershell.