PowerShell += on foreach loop duplicates previously added data - excel

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

Related

Active Directory get all user's logon hours(when they're allowed to log in) using powershell

Is it possible to get all the AD user's logon hours and output it into an excel spreadsheet. So something like:
Name
Logon Hours
Bob
9am-6pm
Jane
8am-5pm
Chris
9am-6pm
So far I have Get-AdUser -Filter * -Properties LogonHours | ft Name, LogonHours. However the output is all in binary.
However the output is all in binary.
That happens to be the way AD stores it. The logonHours attribute value consists of 21 bytes, each byte covering an 8-hour window, starting from midnight on Sunday.
As TheMadTechnician notes, converting the byte values to binary/base-2 strings might be the simplest way of accessing each "hour".
With this in mind, we could make a nifty little helper class that can translate the byte array into meaningful logon hour information:
class LogonHourAdapter
{
hidden
[string[]]$_days
LogonHourAdapter([byte[]]$logonHours)
{
if($logonHours.Count -ne 21){
throw [System.ArgumentException]::new('logonHours', "Expected byte array of length 21.")
}
$this._days = [string[]]::new(7)
for($i = 0; $i -lt 7; $i++){
$offset = $i * 3
$this._days[$i] = ($logonHours[$offset..($offset+2)]|ForEach-Object{[convert]::ToString($_,2).PadLeft(8, '0')})-join''
}
}
[bool]
IsAllowedToLogonDuring([DayOfWeek]$day, [int]$hour)
{
if($hour -ge 24){
throw [System.ArgumentOutOfRangeException]::new('hour')
}
return $this._days[$day][$hour] -eq '1'
}
[int[]]
GetLogonHoursOn([DayOfWeek]$day){
$hours = 0..23 |? {$this._days[$day][$_] -eq '1'}
return $hours -as [int[]]
}
}
Now we don't need to worry about parsing the binary data, we just pass the attribute value to an instance of the class:
PS ~> $bob = Get-ADUser Bob -Properties logonHours
PS ~> $bobHours = [LogonHourAdapter]::new($bob.logonHours)
PS ~> $bobHours.IsAllowedToLogonDuring('Monday', 9) # Bob is allowed to logon after 9am
True
PS ~> $bobHours.IsAllowedToLogonDuring('Monday', 6) # But not earlier!
False
Next, we'll want to track "ranges" - contiguous periods throughout each day where the user is allowed to logon.
For each day, simply "walk" through each hour from 0 through 23 and start tracking a new range everytime there's a break:
# Prepare our logon hour adapter
$lha = [LogonHourAdapter]::new($bob.logonHours)
# Extract ranges for each day of the week
$ranges = foreach($day in -split 'Sunday Monday Tuesday Wednesday Thursday Friday Saturday'){
$currRange = $null
0..23|%{
if($lha.IsAllowedToLogonDuring($day, $_)){
if(-not $currRange){
$currRange = [pscustomobject]#{ Day = [DayOfWeek]$day; From = $_; To = $null}
}
$currRange.To = $_ + 1
}else{
if($currRange){
$currRange
$currRange = $null
}
}
}
if($currRange){
$currRange
}
}
Now we just need to group them together so we can express "8-16 (Monday, Tuesday, Friday)" instead of "8-16 (Monday), 8-16 (Tuesday), 8-16 (Wednesday) ... etc.":
$label = $ranges |Group-Object From,To |Sort Count -Descending |ForEach-Object {
'{0:00}-{1:00} ({2})' -f $_.Group[0].From,$_.Group[0].To,(($_.Group.Day |Sort) -join', ')
}
And $label now contains a more human readable version of Bob's logonHours:
PS ~> $label
08-16 (Monday, Tuesday, Wednesday, Thursday, Friday, Saturday)

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 convert a string to an integer in PowerShell?

I am trying to create a string from a variable in PowerShell. I want to display numbers as strings and not as integers.
I have tried putting [str] before my variable, like for string to integer ([int]), but that gave me an error. I also searched my issue but no one has asked a question like this.
The code in question is the "$jstr" line.
for ($j = 0; $j -lt 1000; $j++)
{
$jstr = [str]$j
$dot = "."
$num = $jstr + $dot
Write-Host $num, Get-Random -SetSeed $j
}
I want the output to be something like "1. Random number", with 1 being the seed number and Random number being the number mapped to that seed,
A couple ways...
If you have a number expressed as a string, you can call string's toInt32() method:
You can assign it to a variable with the type identifier:
However, this ONLY works if the string is ALL numbers (no non-numeric/non-hex characters).
If you want to go the other way (express an integer as a string), use the toString() method:
$integer.toString()
Like this?
for ($j = 0; $j -lt 6; $j++)
{
$rand = Get-Random -SetSeed $j
"$j.$rand"
}
0.1866861594
1.42389573
2.365335408
3.688215708
4.1011161543
5.1334173171
here's one way to get a list with the seed on the left, a dot, and then the random number. it uses the -f string format operator to place the numbers and to align the numbers in the allotted space. [grin]
$Start = 0
$End = 10
$SLength = ([string]$Count).Length
$RMin = 0
$RMax = 1e3
$RLength = ([string]$RMax).Length
foreach ($Number in $Start..$End)
{
"{0,$SLength}. {1,$RLength}" -f $Number, (Get-Random -SetSeed $Number -InputObject ($RMin..$RMax))
}
output ...
0. 607
1. 602
2. 997
3. 636
4. 143
5. 839
6. 6
7. 404
8. 536
9. 394
10. 124

Proper spreadsheet build with Excel::Writer::XLSX in perl

Being a new perl user, it's hard to understand everything with not much experience.
Bellow is a portion of a perl script which is supposed to build a spreadsheet from parsing a text file generated by a custom software . After execution, the xlsx is built by perl, the product group is populated in each row of the designated column, and the product is inserted in the next column. When I have too many products they end up being inserted in the next column, and the next column, and on and on, of the given group.
Ideally I would like to have the multiple products inserted in the next row of the same products column.
Currently:
==========================
Group | Product_1 | Prodcut_2 |
===========================
Ideally:
==================
Group | Product1 |
==================
(blank) | Product2 |
==================
(blank) | Product3 |
==================
How would this be accomplished?
Below is a snippet of the current code:
my $product_regex ='^Newly listed product: (.*)$';
if ($line =~ /$product_regex/) {
$change = $1;
if ($some_flag == 1) {
$some_flag = 0;
push(#{ $product_changes{$group} } , $change);
}
}
my $format = $workbook->add_format();
$format->set_text_wrap();
my $worksheet = $workbook->add_worksheet( 'Sheet1' );
# Writing 2 column headers
$worksheet->write( 0, 0, 'Group' );
$worksheet->write( 0, 1, 'Product' );
my $row;
$row = 1;
for my $key (sort keys %product_changes) {
# To avoid evaluation from Excel, $key must be placed in quotes
$worksheet->write($row, 0, $key, $format );
$worksheet->write($row, 1, $product_changes{"$key"},$format );
$row++;
Not sure if you are looking for help in parsing, or writing the output.
Here is a way to write the output, assuming you have it parsed into a hash of arrayrefs, group => [ product_change, product_change, ...]
my $row = 1;
for my $key (sort keys %product_changes) {
$worksheet->write($row, 0, $key, $format);
for my $pc (#{$product_changes{"$key"}}) {
$worksheet->write($row++, 1, $pc, $format);
}
}

Calculating the Mean from aPerl Script

I m still in here. ;)
I've got this code from a very expert guy, and I'm shy to ask him this basic questions...anyway this is my question now; this Perl Script prints the median of a column of numbers delimited space, and, I added some stuff to get the size of it, now I'm trying to get the sum of the same column. I did and got not results, did I not take the right column? ./stats.pl 1 columns.txt
#!/usr/bin/perl
use strict;
use warnings;
my $index = shift;
my $filename = shift;
my $columns = [];
open (my $fh, "<", $filename) or die "Unable to open $filename for reading\n";
for my $row (<$fh>) {
my #vals = split/\s+/, $row;
push #{$columns->[$_]}, $vals[$_] for 0 .. $#vals;
}
close $fh;
my #column = sort {$a <=> $b} #{$columns->[$index]};
my $offset = int($#column / 2);
my $length = 2 - #column % 2;
my #medians = splice(#column, $offset, $length);
my $median;
$median += $_ for #medians;
$median /= #medians;
print "MEDIAN = $median\n";
################################################
my #elements = #{$columns->[$index]};
my $size = #elements;
print "SIZE = $size\n";
exit 0;
#################################################
my $sum = #{$columns->[$index]};
for (my $size=0; $size < $sum; $size++) {
my $mean = $sum/$size;
};
print "$mean\n";
thanks in advance.
OK some pointers to get you going :
You can put all the numbers into an array :
my #result = split(m/\d+/, $line);
#average
use List::Util qw(sum);
my $sum = sum(#result);
You can then access individual columns with $result[$index] where index is the number of column you want to access.
Also note that :
$total = $line + $total;
$count = $count + 1;
Can be rewritten as :
$total += $line;
$count += 1;
Finally make sure that you are reading the file :
put a "debugging" print into the while loop :
print $line, "\n";
This should get you going :)

Resources