Alter output using exported Excel/CSV data if null values are found within 'foreach' - excel

Sorry I wasn't quite sure how to word the question.
This is a follow-on from a previous question here: Take Excel cell content and place it into a formatted .txt file
The Import-XLS function I'm using is from here:
https://gallery.technet.microsoft.com/office/17bcabe7-322a-43d3-9a27-f3f96618c74b
My current code looks like this:
. .\Import-XLS.ps1
$OutFile = ".\OutTest$(get-date -Format dd-MM).txt"
$Content = Import-XLS '.\DummyData.xlsx'
$Content | foreach-object{
$field1 = $("{0},{1},{2},{3},{4},{5},{6}" -f "Field1", $_."Value1", $_."Value2", $_."Value3", $_."Value4", $_."Value5", $_."Value6")
$field1.Split(",",[System.StringSplitOptions]::RemoveEmptyEntries) -join ","
$field2 = $("{0},{1}" -f "Field2", $_."Value1")
$field2.Split(",",[System.StringSplitOptions]::RemoveEmptyEntries) -join ","
} | Out-File $OutFile
My Dummydata is essentially this (I've inserted $null to point out the blank values)
Entries Value1 Value2 Value3 Value4 Value5 Value6 Value7 Value8
Entry 1 1 2 $null 4 5 6 7 8
Entry 2 $null B A B A B A B
So I've managed to have the code 'ignore/skip' a null value within a set.
My output looks like this
Field1,1,2,4,5,6
Field2,1
Field1,B,A,B,A,B
Field2
What I would like help with now is how to either remove "Field2" because it has no value, or comment it out using ;.
So my output would look like
Field1,1,2,4,5,6
Field2,1
Field1,B,A,B,A,B
or
Field1,1,2,4,5,6
Field2,1
Field1,B,A,B,A,B
;Field2
Essentially, if a row has no data in any of it's fields that are being written for that line, it should be ignored.
Thanks SO MUCH for your help.
EDIT:
I've discovered I need to remove the comma "," between the {0},{1} and use a space instead. So I'm using
$field2 = $("{0} {1}" -f "Field 2", $_."Value1")
$field2 = $field2.Split(" ",[System.StringSplitOptions]::RemoveEmptyEntries)
if ( $field2.Count -le 1) { ";$field2" } else { $field2 -join "`t`t" }
Which works for 'most' of my fields.
However there are 'some' Fields and Values that have spaces in them.
Additionally there some values like "TEST TEXT"
So now I'm getting
Field1 3,B,A,B,A,B
Field 2 TEST TEXT
Instead of (quotes for clarity)
"Field1" 3,B,A,B,A,B
"Field 2" "TEST TEXT"
I'm happy to just use some kind of exception only for these few fields.
I've tried a few other things, but I end up breaking the IF statement, and it ;comments out fields with values, or doesn't ;comment out fields with no values.

Next code snippet could help:
### … … … ###
$Content | foreach-object{
$field1 = $("{0},{1},{2},{3},{4},{5},{6}" -f "Field1", $_."Value1", $_."Value2", $_."Value3", $_."Value4", $_."Value5", $_."Value6")
$auxarr = $field1.Split(",",[System.StringSplitOptions]::RemoveEmptyEntries)
if ( $auxarr.Count -le 1) { ";$auxarr" } else { $auxarr -join "," }
$field2 = $("{0},{1}" -f "Field2", $_."Value1")
$auxarr = $field2.Split(",",[System.StringSplitOptions]::RemoveEmptyEntries)
if ( $auxarr.Count -le 1) { ";$auxarr" } else { $auxarr -join "," }
} | Out-File $OutFile
Edit to answer additional (extending) subquestion I need to remove the comma "," between the {0},{1} and use a space instead:
$field2 = $("{0},{1}" -f "Field 2", $_."Value1")
### keep comma ↑ ↓ or any other character not contained in data
$field2 = $field2.Split(",",[System.StringSplitOptions]::RemoveEmptyEntries)
if ( $field2.Count -le 1) { ";$field2" } else { $field2 -join "`t`t" }
If your data contain a significant comma then use Split(String(), StringSplitOptions) form of String.Split Method e.g. as follows:
$burstr = [string[]]'#-#-#'
$field2 = $("{0}$burstr{1}" -f "Field 2", $_."Value1")
$field2 = $field2.Split($burstr,[System.StringSplitOptions]::RemoveEmptyEntries)
if ( $field2.Count -le 1) { ";$field2" } else { $field2 -join "`t`t" }

Related

PowerShell Regex get multiple substrings between 2 strings and write them to files with sequence numbers

Old thread
My question regards:
function GetStringBetweenTwoStrings($firstString, $secondString, $importPath){
#Get content from file
$file = Get-Content $importPath
#Regex pattern to compare two strings
$pattern = "$firstString(.*?)$secondString"
#Perform the opperation
$result = [regex]::Match($file,$pattern).Groups[1].Value
#Return result
return $result
}
GetStringBetweenTwoStrings -firstString "Lorem" -secondString "is" -importPath "C:\Temp\test.txt"
This is nice for only one -firstString and -secondString, but how to use this function to chronologically write multiple same strings in numbered TXT?
txt - file(with more sections of text):
Lorem
....
is
--> write to 001.txt
Lorem
....
is
--> write to 002.txt
and so forth....
And the structure of the section is preserved and is not in one line.
I hope someone can tell me that. Thanks.
The function you quote has several limitations (I've left feedback on the original answer), most notably only ever reporting one match.
Assuming an improved function named Select-StringBetween (see source code below), you can solve your problem as follows:
$index = #{ value = 0 }
Get-ChildItem C:\Temp\test.txt |
Select-StringBetween -Pattern 'Lorem', 'is' -Inclusive |
Set-Content -LiteralPath { '{0:000}.txt' -f ++$index.Value }
Select-StringBetween source code:
Note: The syntax is in part patterned after Select-String. After defining the function, run Select-StringBetween -? to see its syntax; the parameter names are hopefully self-explanatory.
function Select-StringBetween {
[CmdletBinding(DefaultParameterSetName='String')]
param(
[Parameter(Mandatory, Position=0)]
[ValidateCount(2, 2)]
[string[]] $Patterns,
[Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName='File')]
[Alias('PSPath')]
[string] $LiteralPath,
[Parameter(Mandatory, ValueFromPipeline, ParameterSetName='String')]
[string] $InputObject,
[switch] $Inclusive,
[switch] $SimpleMatch,
[switch] $Trim
)
process {
if ($LiteralPath) {
$InputObject = Get-Content -ErrorAction Stop -Raw -LiteralPath $LiteralPath
}
if ($Inclusive) {
$regex = '(?s)(?:{0}).*?(?:{1})' -f
($Patterns[0], [regex]::Escape($Patterns[0]))[$SimpleMatch.IsPresent],
($Patterns[1], [regex]::Escape($Patterns[1]))[$SimpleMatch.IsPresent]
}
else {
$regex = '(?s)(?<={0}).*?(?={1})' -f
($Patterns[0], [regex]::Escape($Patterns[0]))[$SimpleMatch.IsPresent],
($Patterns[1], [regex]::Escape($Patterns[1]))[$SimpleMatch.IsPresent]
}
if ($Trim) {
[regex]::Matches(
$InputObject,
$regex
).Value.Trim()
}
else {
[regex]::Matches(
$InputObject,
$regex
).Value
}
}
}
Note that there's also a pending feature request on GitHub to add this functionality directly to Select-String - see GitHub issue #15136

PowerShell += on foreach loop duplicates previously added data

So i have an excel file like this
Document Number, Qty, Price
1111-01,1,3.00
1112-00A,2,4.00
And what I am doing is importing it into powershell, the going line by line.
If the quantity is ever greater than 1, I have to duplicate that line that many times whlie changing the quantity to 1 each time and updateing the document number so its unique on each line. I am then adding to an array so i can at the very end export as an excel file.
$report = Import-Excel "pathToFile.xlsx"
$report2 = #()
foreach($line in $report){
$report2+=$line.PSObject.Copy()
}
$template = #()
foreach($line in $report2)
...
some irrelevant code
...
if($line.Qty -gt 1){
$line2 = $line.PSObject.Copy()
$ogInvoice = $line2.'Document Number'.Split("-")[0]
$invoiceAfter = $line2.'Document Number'.Split("-")[1]
if($invoiceAfter -match "^*[A-Z]$"){
$letter = $invoiceAfter.Substring($invoiceAfter.Length-1,1)
}else{
$letter = ""
}
$qty = $line2.Qty
$line2.Qty = 1
$counterQty = 0
while($counterQty -lt $qty){
$invoiceLastTwoNumber = [int]('{0:d2}' -f[int] $invoiceAfter.Substring(0,2)) + $counter
$line2.'Document Number' = (-join($ogInvoice,"-",$invoiceLastTwoNumber.ToString(),$letter))
$counter = $counter + 1
$template+=$line2
$counterQty = $counterQty + 1
}
}
The problem is that after checking the progress, the first time i add the line, the document number is 1112-50A like it should be, then the next time I add the line into $template, the document number is 1112-51A but it updates the previously added line.
So i get
1111-01,1,3.00
1112-51A,1,4.00
1112-51A,1,4.00
Instead of what i want which is:
1111-01,1,3.00
1112-50A,1,4.00
1112-51A,1,4.00
NOTE: the extra coding like PSObject.Copy is other stuff i found online because apparently iterating over the $report is more like a pointer.
If I understand correctly, you're looking to repeat the current object as many times as .Qty only if .Qty is greater than 1 and in addition, update the property Value to 1.
In addition, seems like you're looking to increment the last digits of the property values of Document Number.
Leaving aside the extra code you are currently showing us and focusing only on the question being asked, this is how you could accomplish it, using $csv as an example of your source data.
$csv = #'
Document Number,Qty,Price
1111-01,1,3.00
1112-00A,2,4.00
1113-15A,4,5.00
'# | ConvertFrom-Csv
$re = [regex] '(\d+)(?=[A-Z]$)'
$output = foreach($line in $csv) {
if($line.Qty -gt 1) {
$loopCount = $line.Qty
$line.Qty = 1
for($i = 0; $i -lt $loopCount; $i++) {
$newLine = $line.PSObject.Copy()
$docNumber = $newLine.'Document Number'
$newLine.'Document Number' = $re.Replace($docNumber, {
param($s)
($i + $s.Groups[1].Value).ToString('D2')
})
$newLine
}
continue
}
$line
}
The expected output from the example $csv would be:
Document Number Qty Price
--------------- --- -----
1111-01 1 3.00
1112-00A 1 4.00
1112-01A 1 4.00
1113-15A 1 5.00
1113-16A 1 5.00
1113-17A 1 5.00
1113-18A 1 5.00

Powershell: Count the occurence of every single "1" string in a txt file

We have a text file of students and their notes and we have to count how many "1" notes all the students have got.
My code shows how many lines contain the "1" note, but when it finds a "1", it jumps to the next line.
Could you help me please?
for example:
Huckleberry Finn 2 1 4 1 1
Tom Sawyer 3 2 1 4 1
It should be 5, but it gets 2.
$ones = 0
$file= Get-Content notes.txt
foreach ($i in $file) {
if ($i.Split(' ') -eq 1){
$ones ++
}
}
$ones
If all the 1 tokens are whitespace-separated in your input file, as the sample content suggests, try:
# With your sample file content, $ones will receive the number 5.
$ones = (-split (Get-Content -Raw notes.txt) -eq '1').Count
The above uses the unary form of -split, the string splitting operator and the -Raw switch of the Get-Content cmdlet, which loads a file into memory as a single, multi-line string.
That is, the command above splits the entire file into white-space separated tokens, and counts all tokens that equal '1', across all lines.
If, instead, you meant to count the number of '1' tokens per line, use the following approach:
# With your sample file content, $ones will receive the following *array*:
# 3, 2
$ones = Get-Content notes.txt | ForEach-Object { ((-split $_) -eq '1').Count }
As for what you tried:
if ($i.Split(' ') -eq 1)
While $i.Split(' ') does return all (single-)space-separated tokens contained in a single input line stored in $i, using that expression in a conditional expression of an if statement only results in one invocation of the associated { ... } block and therefore increments $ones only by a value of 1, not the number of 1 tokens in the line at hand.
Solved!
Thank you mklement0!
I don't understand why, but it works so:
$ones = 0
$file= Get-Content notes.txt
foreach ($i in $file) {
$ones=(-split ($i) -eq '1').Count
}
}
$ones

how to add string to the start of each chunk of fixed-line text file in PowerShell?

I have a text file which is comprised of only one line. I have had much trouble with splitting the file into a specific number of characters, then adding a string in front of each chunk of characters.
With a multi-line file, I can add characters to each line very easily using
Get-Content -Path $path | foreach-object {$string + $_} | out-file $output but it is much more complicated with a file with only one line.
For example, if I had a file with these random characters,
(******************************************) and i wanted to add a string to the start of every 10 chars, then it would look like this, (examplestring**********examplestring**********examplestring**********) and so on. I have researched everywhere but I have just managed to add the chars to the end of each chunk of characters.
Does anyone have a way of doing this? Preferably using streamreader and writer as get-content may not work for very large files. Thanks.
Hmm, there are some dynamic parameters applicable to file-system get-content and set-content commands that are close to what you are asking for. For example, if test.txt contains a number of * characters, you might interleave every four * with two + characters with something like this:
get-content .\test.txt -Delimiter "****" | % { "++$_" } | Set-Content test2.txt -NoNewline
I don't know how close that is to a match for what you want, but it's probably useful to know that some of these provider-specific parameters, like '-Delimiter' aren't obvious. See https://technet.microsoft.com/en-us/library/hh847764.aspx under the heading 'splitting large files'.
Alternatively, here's a quick function that reads length-delimited strings from a file.
Set-StrictMode -Version latest
function read-characters( $path, [int]$charCount ) {
begin {
$buffer = [char[]]::new($charCount)
$path = Join-Path $pwd $path
[System.IO.StreamReader]$stream = [System.IO.File]::OpenText($path)
try {
while (!$stream.EndOfStream) {
$len = $stream.ReadBlock($buffer,0,$charCount);
if ($len) {Write-Output ([string]::new($buffer,0,$len))}
}
} catch {
Write-Error -Exception $error[0]
} finally {
[void]$stream.Close()
}
}
}
read-characters .\test.txt -charCount 10 |
% {"+$_"} |
write-host -NoNewline
It could use some parameter checking, but should get you started...
With a manageable file size, you might want to try something like this:
$directory = "C:\\"
$inputFile = "test.txt"
$reader = new-object System.IO.StreamReader("{0}{1}" -f ($directory, $inputFile))
# prefix string of each line
$startString = "examplestring"
# how many chars to put on each line
$range = 10
$outputLine = ""
$line = $reader.ReadLine()
$i = 0
while ($i -lt $line.length) {
$outputLine += $($startString + $line.Substring($i, [math]::min($range, ($line.length - $i))))
$i += $range
}
$reader.Close()
write-output $outputLine
Basically it's using substring to cut out each chunk, prefixing the chumk with given string, and appending to the result variable.
Sample input:
==========================
Sample output:
examplestring==========examplestring==========examplestring======

Why file path now getting concatenate in variable

I am writing powershell script to find few string in a path. It returns file path correctly in variable. Now if I try to add it to some other variable it adds empty (adds nothing), but if I print variable separately it gets print correctly.
My code,
$final = ""
foreach($e in $stringIDColum)
{
$e
$var = (Get-ChildItem “C:\path” -recurse -exclude $excluded | Select-String -pattern $e | group path | select name)
$final += "," + $e + "," + $var
}
Here value of $e is getting added, $var is not getting added. But if I print $var separately it prints path correctly.
Secondly I have tried various ways, to print line number, but it does not print line number in front of path.
I think the issue is that $var is potentially an array. So you need to convert it a string. Check the type once it's created.

Resources