powershell - mystery whitespace from output of loops - powershell

I am getting whitespace added to my output from the following section of code. I need to format this a certain way so for sake of finding out where this whitespace is coming from I am just outputting the variables. I even added .trim() to make sure it wasn't coming from the variables themselves. Where in the heck is this whitespace coming from?
#sort by Name and sort Values of each
$output += foreach($icon in $table.GetEnumerator() | sort -Property Name) {
$icon.Name.trim()
foreach($type in $icon.Value | sort) {
$fa_types[$type].trim()
}
}
#Output to file
"version: " + $fa_version + "`r`nicons:`r`n" + $output | Out-File $output_file
example output file :
version: 5.13.0
icons:
arrow-circle-right solid regular calendar-week regular users solid usb-drive solid file-alt regular key solid user-graduate solid comment-dots regular plus solid calendar-check regular spinner regular stopwatch regular file-search solid user-chart solid map-marker-alt regular calculator regular apple brands
Running powershell version 5 on Windows 10.

That's a strange way to create a string... I recommend a safer way, where no output functions of PowerShell are involved:
#sort by Name and sort Values of each
$output = ""
foreach($icon in $table.GetEnumerator() | sort -Property Name) {
$output += $icon.Name
foreach($type in $icon.Value | sort) {
$output += $fa_types[$type]
}
}
#Output to file
"version: " + $fa_version + "`r`nicons:`r`n" + $output | Out-File $output_file

Reason this is happening is because you are printing an array within a string. When you loop over your items and print just the $fa_types[$type], it writes it as item of an array to the $output.
If you print only $output, you will see multiple items separated new line but if you put that inside of a string, its represented by a space delimiter.
Examples:
$outp = foreach($var in (0..5)) { $var }
$outp
# shows the following output
# 0
# 1
# 2
# 3
# 4
# 5
Write-Output "string $outp end"
# prints it in a single line
# string 0 1 2 3 4 5 end
You can concatenate your array via a join so no spaces are printed in your output.
"version: " + $fa_version + "`r`nicons:`r`n" + $output -join "" | Out-File $output_file
#or
"version: " + $fa_version + "`r`nicons:`r`n" + -join $output | Out-File $output_file

Related

How can I loop through each record of a text file to replace a string of characters

I have a large .txt file containing records where a date string in each record needs to be incremented by 2 days which will then update the field to the right of it which contains dashes --------- with that date. For example, a record contains the following record data:
1440149049845_20191121000000 11/22/2019 -------- 0.000 0.013
I am replacing the -------- dashes with 11/24/2019 (2 days added to the date 11/22/2019) so that it shows as:
1440149049845_20191121000000 11/22/2019 11/24/2019 0.000 0.013
I have the replace working on a single record but need to loop through the entire .txt file to update all of the records. Here is what I tried:
$inputRecords = get-content '\\10.12.7.13\vipsvr\Rancho\MRDF_Report\_Report.txt'
foreach ($line in $inputRecords)
{
$item -match '\d{2}/\d{2}/\d{4}'
$inputRecords -replace '-{2,}',([datetime]$matches.0).adddays(2).tostring('MM/dd/yyyy') -replace '\b0\.000\b','0.412'
}
I get an PS error stating: "Cannot convert null to type "System.DateTime"
I'm sorry but why are we using RegEx for something this simple?
I can see it if there are differently formatted lines in the file, you'd want to make sure you aren't manipulating unintended lines, but that's not indicated in the question. Even still, it doesn't seem like you need to match anything within the line itself. It seems like it's delimited on spaces which would make a simple split a lot easier.
Example:
$File = "C:\temp\Test.txt"
$Output =
ForEach( $Line in Get-Content $File)
{
$TmpArray = $Line.Split(' ')
$TmpArray[2] = (Get-Date $TmpArray[1]).AddDays(2).ToString('M/dd/yyyy')
$TmpArray -join ' '
}
The 3rd element in the array do the calculation and reassign the value...
Notice there's no use of the += operator which is very slow compared to simply assigning the output to a variable. I wouldn't make a thing out of it but considering we don't know how big the file is... Also the String format given before 'mm/dd/yyyy' will result in 00 for the month like for example '00/22/2019', so I changed that to 'M/dd/yyyy'
You can still add logic to skip unnecessary lines if it's needed...
You can send $Output to a file with something like $Output | Out-File <FilePath>
Or this can be converted to a single pipeline that outputs directly to a file using | ForEach{...} instead of ForEach(.. in ..) If the file is truly huge and holding $Output in memory is an issue this is a good alternative.
Let me know if that helps.
You mostly had the right idea, but here are a few suggested changes, but not exactly in this order:
Use a new file instead of trying to replace the old file.
Iterate a line at a time, replace the ------, write to the new file.
Use '-match' instead of '-replace', because as you will see below that you need to manipulate the capture more than a simple '-replace' allows.
Use [datetime]::parseexact instead of trying to just force cast the captured text.
[string[]]$inputRecords = get-content ".\linesource.txt"
[string]$outputRecords
foreach ($line in $inputRecords) {
[string]$newLine = ""
[regex]$logPattern = "^([\d_]+) ([\d/]+) (-+) (.*)$"
if ($line -match $logPattern) {
$origDate = [datetime]::parseexact($Matches[2], 'mm/dd/yyyy', $null)
$replacementDate = $origDate.adddays(2)
$newLine = $Matches[1]
$newLine += " " + $origDate.toString('mm/dd/yyyy')
$newLine += " " + $replacementDate.toString('mm/dd/yyyy')
$newLine += " " + $Matches[4]
} else {
$newLine = $line
}
$outputRecords += "$newLine`n"
}
$outputRecords.ToString()
Even if you don't use the whole solution, hopefully at least parts of it will be helpful to you.
Using the suggested code from adamt8 and Steven, I added to 2 echo statements to show what gets displayed in the variables $logpattern and $line since it is not recognizing the pattern of characters to be updated. This is what displays from the echo:
Options MatchTimeout RightToLeft
CalNOD01 1440151020208_20191205000000 12/06/2019 12/10/2019
None -00:00:00.0010000 False
CalNOD01 1440151020314_20191205000000 12/06/2019 --------
None -00:00:00.0010000 False
this is the rendered output:
CalNOD01 1440151020208_20191205000000 12/06/2019 12/10/2019
CalNOD01 1440151020314_20191205000000 12/06/2019 --------
This is the code that was used:
enter image description here

Remove particular characters from lines and concatenate them

I have a problem where I need to cut specific characters from a line and then concatenate the line with the next lines, separated by commas.
Consider there is a text file abc.txt and I need the last 3 lines from the file. The last 3 lines are in the this format:
11/7/2000 17:22:54 - Hello world.
19/7/2002 8:23:54 - Welcome to the new technology.
24/7/2000 9:00:13 - Eco earth
I need to remove the starting time stamp from each line and then concatenate the lines as
Hello world.,Welcome to the new technology,Eco earth.
The time stamp is not static and I want to make use of a regex
I tried the following:
$Words = (Get-Content -Path .\abc.txt|Select-Object -last 3|Out-String)
$Words = $Words -split('-')
$regex = "[0-9]{1,2}/[0-9]{1,2}/[0-9]{1,4} [0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2}):[0-9]{1,3}"
The output I used to get is like
11/7/2000 17:22:54
Hello world
19/7/2002 8:23:54
Welcome to the new technology.
24/7/2000 9:00:13
Eco earth
There is no need to create a Regex that tries to figure out the timestamp part, because you want to skip that anyway.
This should work:
# read the file and get the last three lines as string array
$txt = Get-Content -Path 'D:\abc.txt' -Tail 3
# loop through the array and change the lines as you go
for ($i = 0; $i -lt $txt.Count; $i++) {
$txt[$i] = ($txt[$i] -split '-', 2)[-1].Trim()
}
# finally, join the array with commas
$txt -join ','
Output:
Hello world.,Welcome to the new technology.,Eco earth
try this:
Get-Content "C:\temp\example.txt" | %{
$array=$_ -split "-", 2
$array[1].Trim()
}
When you have for example : "DATE - blablabla"
If you do .Split("-") on it you get :
Date
blablabla
What you can do is $string.Split("-")[Which_Line] -> so
$string="12/15/18 08:05:10 - Hello World."
$string=$string.Split("-")[1]
Returns : Hello world. (with spaces before)
Now on string you can apply Trim() function - it removes spaces before and after your string
$string=$string.Trim()
Gives you Hello world.
For your answer, if it's static usage (always 3) :
$Words = (Get-Content -Path .\abc.txt|Select-Object -last 3|Out-String).Split("-")
$end=$Words[2].Trim() + "," + $Words[4].Trim() + "," + $Words[6].Trim()

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)

Entering a string with new line characters into excel using powershell

I have a string str.
$str="abcd_1
abcd_2
abcd_3"
First in the for-loop I am concatenating the string and making a full string of Id's with a carriage return+newline character.
And on splitting I am using just the new line character.
I am getting a space in front in the data which is entered from the second cell.
for($intRow = $trow ; $intRow -le $maxRow ; $intRow++){
$codeName = $currentCode
$fin = $codeName + "_" + $i + "`r`n"
$finCode=$finCode+$fin
$i= $i + 1
}
$currentSheet.Cells.Item($fRow,$currentCol).Value2 = $finCode
$clipboardData = $finCode.Split("`n").TrimStart()
$newClipboardData = $clipboardData.Where({$_.TrimStart() -ne ""}).ForEach({$_.TrimStart()})
[System.Windows.Forms.Clipboard]::SetText($newClipboardData)
$currentSheet.Cells.Item($fRow,$currentCol).Select() | Out-Null
$currentSheet.Paste() | Out-Null
Just to be more clear and precise. This example shows how to split the string by new line character. Just increase the $intRow every time to write in next row:
ForEach($strValue In $str.Split("`n"))
{
$currentSheet.Cells.Item($intRow,$currentCol).Value2 = $strValue
$intRow++
}
New Code:
[System.Windows.Forms.Clipboard]::SetText($str.Split("`n"))
$currentSheet.Cells.Item($intRow,$currentCol).Select() | Out-Null
$currentSheet.Paste() | Out-Null
You can eliminate spaces using trim. But splitting using 'r'n is not giving me correct data. It is returning a single line.:
$clipboardData = $str.Split("`n").TrimStart()
$newClipboardData = $clipboardData.Where({$_.TrimStart() -ne ""}).ForEach({$_.TrimStart()})
[System.Windows.Forms.Clipboard]::SetText($newClipboardData)
When I checked the contents of the values it does not have any leading spaces but it shows a single space when pasted in excel.
$newClipboardData | %{ "-$_-" }
-Some Text-
-Some text With spaces -
-Some text again-

Powershell fixed width export

I am having a text file wich uses fixed width for separating columns.
I'm loading the file and create a new column which concatinates the values of the first two columns.
The problem I have that when exporting the data I need to define a fixed column width of 13 for Column C.
Column A (3) Column B(9) Column C(13)
MMA 12345 12345_MMA
MMO 987222 987222_MMO
Basically for this example in the export I am missing 4 spaces for the first row and 3 for the second row.
Thisis my current code, which also includes a new row for MD5 creation.
# Load input data
$PreSystem = [IO.File]::ReadAllText("C:\FILE.txt")
# Initiate md5-hashing
$md5 = new-object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider
$utf8 = new-object -TypeName System.Text.UTF8Encoding
# Split input data by lines
$all = $PreSystem.split("`n")
# Loop over lines
for($i = 0; $i -lt $all.length-1; $i += 1) {
# Access distinct lines
$entry = "$($all[$i])"
# Get the different parameters
$market_code = $entry.substring(1,3)
$soc = $entry.substring(4,9)
# Hash the SOC element
$hash = [System.BitConverter]::ToString($md5.ComputeHash($utf8.GetBytes($soc)))
# Create desired format for each entry
$output = $hash.Replace("-","")+$soc.Replace(" ","") + "_" + $market_code + $all[$i]
# Write to file
"$output" | Out-File -Filepath C:\"C:\FILE.txt" -Append -encoding ASCII
}
Thanks in advance
You can create a custom table format using the tip explained here. Here is an example for Get-Process:
$a = #{Expression={$_.Name};Label="Process Name";width=25}, `
#{Expression={$_.ID};Label="Process ID";width=15}, `
#{Expression={$_.MainWindowTitle};Label="Window Title";width=40}
Get-Process | Format-Table $a
Basically, you build an expression through wich Format-Table will pipe
each row. Instead of taking care of the formating yourself for each row, you build a hash and pipe it through Format-Table.
It's still not quite clear to me what output you actually want to achieve, but maybe this will give you some idea.
One of the most convenient ways to get formatted string output is using the format operator (-f). You specify a format string with placeholders in curly brackets, and fill it with the values of an array:
PS C:\> '_{0}:{1}:{2}_' -f 'foo', 'bar', 'baz'
_foo:bar:baz_
Column widths can be specified in the format string like this:
PS C:\> '_{0,-5}:{1,7}:{2,-9}_' -f 'foo', 'bar', 'baz'
_foo : bar:baz _
As you can see, negative values align the column to the left, positive values align it to the right.
If there's a chance that a value is too long for the give column width you need to truncate it, e.g. with the Substring() method:
PS C:\> $s = 'barbarbar'
PS C:\> $len = [math]::Min(7, $s.Length)
PS C:\> '_{0,-5}:{1,7}:{2,-9}_' -f 'foo', $s.Substring(0, $len), 'baz'
_foo :barbarb:baz _
You can quickly have a fixed size left-aligned content string using the following code:
Write-Host "$myvariable $(" " * 60)".Substring(0,60)
this will give you a fixed width of 60 characters with the contents aligned to the left
One of the solutions is for each of the rows use this mechanism when concatenating:
$a = "MMA"
$b = "12345"
$str = "$($b)_$($a)"
if (($str.Length) -ge 13 ) {
Write-Host "$($str)"
} else {
$padStr = " " * (13 - ($str.Length))
Write-Host "$($str)$($padStr)"
}
So instead of Write-Host CmdLet you can use the appropriate CmdLet for your purpose.
Edit, after adding actual code. So the above logic would translate into:
$market_code = $entry.subString(1,3)
$soc = $entry.subString(4,9)
$str = $soc.Replace(" ", "") + "_" + $market_code
if (($str.Length) -ge 13 ) {
$output = $hash.Replace("-","") + $str + $all[$i]
} else {
$padStr = " " * (13 - ($str.Length))
$output = $hash.Replace("-","") + $str + $padStr + $all[$i]
}
You can do fixed size using next code:
$data = "Some text"
$size = 20
$str = [string]::new(' ',$size).ToCharArray()
$data.CopyTo(0,$str,0,$data.Length)
$str = $str -join ''