Joining every two lines in Powershell output - powershell

I'm attempting to combine every two lines of the output of this Powershell command:
(((Invoke-WebRequest -Uri "https://www.timeanddate.com/holidays/us/$(Get-Date -Format yyyy)").Content | sls 'id=holidays') -split '<th class="nw" >' | Out-String -Stream) -replace '<|>',',' | ForEach-Object {$_.Split(',')[10,0];}
As you can see if you run it it outputs holidays and their date for the current year like so:
New Year's Day
Jan 1
World Braille Day
Jan 4
Epiphany
Jan 6
Orthodox Christmas Day
Jan 7
International Programmers' Day
Jan 7
etc.
My goal is for the output to be:
New Year's Day Jan 1
World Braille Day Jan 4
Epiphany Jan 6
Orthodox Christmas Day Jan 7
International Programmers' Day Jan 7
etc.
Any suggestions are welcome (I would like to do this without writing output to a file during the process). Or if there is a more efficient way of doing this I'm open to that as well.

Use a simple for loop with a counter that increments by 2 every time:
$splitLines = (((Invoke-WebRequest -Uri "https://www.timeanddate.com/holidays/us/$(Get-Date -Format yyyy)").Content | sls 'id=holidays') -split '<th class="nw" >' | Out-String -Stream) -replace '<|>',',' | ForEach-Object {$_.Split(',')[10,0];}
for($i = 0; $i -lt $splitLines.Count; $i += 2){
$splitLines[$i,($i+1)] -join ' '
}

You can do something along those lines. For some reason I have to use modulus 4 here and not 2 because when I split like that it makes every second line empty.
$inputData = #"
New Year's Day
Jan 1
World Braille Day
Jan 4
Epiphany
Jan 6
Orthodox Christmas Day
Jan 7
International Programmers' Day
Jan 7
"#
$splitData = $inputData.Split([Environment]::NewLine)
# use a dot here to make the output availabe after the iteration is completed
$splitData | . {
begin {
$output = ""
$i = 0
}
process {
if(0 -eq $i % 4) {
$output += $_ + [Environment]::NewLine
}
$i = $i + 1
}
}
Write-Output $output
# New Year's Day
# World Braille Day
# Epiphany
# Orthodox Christmas Day
# International Programmers' Day
It's not perfect but it works for the given problem I would suggest tinkering with it and make it little more nice. Haven't used PowerShell in a while.

Related

How to convert powershell array to table

I found a similar post regarding the problem in the link below.
How to fetch first column from given powershell array?
I am not able to directly convert it to a table as some fields are missing and do operations.
Customer ID Client Name Computer Name Computer Brand Duration Connection Time Lang
123 first last 127.0.0.1 lenovo 10:00 8/18/2019 6:00 PM Eng
1 lastname 127.0.0.2 apple 2:30:00 8/18/2019 1:00 AM Chn
86 user3 127.0.0.1 dell 8/18/2019 2:00 PM
21 user4 127.0.0.4 apple 30:00 8/17/2019 1:00 PM Eng
I want to first filter with a specific user who is connected for more than 30 minutes and then list its id.
Update
The result should be
1
21
because they are connected for 30min and over.
If the data you show is indeed the output of a Fixed-Width file, you need to try and get the widths for each field in order to parse it. A handicap here is that the original header names contain a space character and we need to replace that by an underscore.
For that, you can use the below function:
function ConvertFrom-FixedWith {
[CmdletBinding()]
Param(
[Parameter(Mandatory = $true, Position = 0)]
[string[]]$Content
)
$splitter = '§¤¶' # some unlikely string: Alt-21, [char]164, Alt-20
$needQuotes = '^\s+|[",]|\s+$' # quote the fields if needed
function _FWClean ([string]$field) {
# internal helper function to clean a field value with regards to quoted fields
$field = $_.Trim() -replace '(?<!\\)\\"|""', '§DQUOTE¶'
if ($field -match '^"(.*)"$') { $field = $matches[1] }
if ($field -match $needQuotes) { $field = '"{0}"' -f $field }
return $field -replace '§DQUOTE¶', '""'
}
# try and calculate the field widths using the first header line
# this only works if none of the header names have spaces in them
# and where the headers are separated by at least one space character.
Write-Verbose "Calculating column widths using first row"
$row = ($Content[0] -replace '\s+', ' ').Trim()
$fields = #($row -split ' ' ) # | ForEach-Object { _FWClean $_ })
$ColumnBreaks = for ($i = 1; $i -lt $fields.Length; $i++) {
$Content[0].IndexOf($fields[$i])
}
$ColumnBreaks = $ColumnBreaks | Sort-Object -Descending
Write-Verbose "Splitting fields and generating output"
$Content | ForEach-Object {
if ($null -ne $_ -and $_ -match '\S') {
$line = $_
# make sure lines that are too short get padded on the right
if ($line.Length -le $ColumnBreaks[0]) { $line = $line.PadRight(($ColumnBreaks[0] + 1), ' ') }
# add the splitter string on every column break point
$ColumnBreaks | ForEach-Object {
$line = $line.Insert($_, $splitter)
}
# split on the splitter string, trim, and dedupe possible quotes
# then join using the delimiter character
#($line -split $splitter | ForEach-Object { _FWClean $_ }) -join ','
}
} | ConvertFrom-Csv # the result is an array of PSCustomObjects
}
With that function in place, parsing the text can be done like so:
$text = #"
Customer_ID Client_Name Computer_Name Computer_Brand Duration Connection_Time Lang
123 first last 127.0.0.1 lenovo 10:00 8/18/2019 6:00 PM Eng
1 lastname 127.0.0.2 apple 2:30:00 8/18/2019 1:00 AM Chn
86 user3 127.0.0.1 dell 8/18/2019 2:00 PM
21 user4 127.0.0.4 apple 30:00 8/17/2019 1:00 PM Eng
"# -split '\r?\n'
# replace the single space characters in the header names by underscore
$text[0] = $text[0] -replace '(\w+) (\w+)', '$1_$2'
# the 'ConvertFrom-FixedWith' function takes a string array as input
$table = ConvertFrom-FixedWith -Content $text
#output on screen
$table | Format-Table -AutoSize
# export to CSV file
$table | Export-Csv -Path 'D:\test.csv' -NoTypeInformation
Output (on screen)
Customer ID Client Name Computer Name Computer Brand Duration Connection Time Lang
----------- ----------- ------------- -------------- -------- --------------- ----
123 first last 127.0.0.1 lenovo 10:00 8/18/2019 6:00 PM Eng
1 lastname 127.0.0.2 apple 2:30:00 8/18/2019 1:00 AM Chn
86 user3 127.0.0.1 dell 8/18/2019 2:00 PM
21 user4 127.0.0.4 apple 30:00 8/17/2019 1:00 PM Eng
If your input $text is already a string array storing all the ines as we see them in your question, then leave out the -split '\r?\n'
Having parsed the input to a table of PsCustomObjects, you can get the customers that are connected for 30 minutes or more with the help of another small helper function:
function Get-DurationInMinutes ([string]$Duration) {
$h, $m, $s = (('0:{0}' -f $Duration) -split ':' | Select-Object -Last 3)
return [int]$h * 60 + [int]$m
}
($table | Where-Object { (Get-DurationInMinutes $_.Duration) -ge 30 }).Customer_ID
This will output
1
21
Update
Now that we finally know the data is from a TAB delimited CSV file, you don't need the ConvertFrom-FixedWith function.
Simply import the data using if it comes from a file
$table = Import-Csv -Path 'D:\customers.csv' -Delimiter "`t"
Or, if it comes from the output of another command as string or string array:
$table = $original_output | ConvertFrom-Csv -Delimiter "`t"
Then, use the Get-DurationInMinutes helper function just like above to get the Customer ID's that are connected for more than 30 minutes:
function Get-DurationInMinutes ([string]$Duration) {
$h, $m, $s = (('0:{0}' -f $Duration) -split ':' | Select-Object -Last 3)
return [int]$h * 60 + [int]$m
}
($table | Where-Object { (Get-DurationInMinutes $_.Duration) -ge 30 }).'Customer ID'
Uhh. I'm surprised there's not a canonical way to do this. Based on https://www.reddit.com/r/PowerShell/comments/211ewa/how_to_convert_fixedwidth_to_pipedelimited_or/.
# 0 19 38 59 81 97 120 123
# Customer ID Client Name Computer Name Computer Brand Duration Connection Time Lang
# 123 first last 127.0.0.1 lenovo 10:00 8/18/2019 6:00 PM Eng
# 1 lastname 127.0.0.2 apple 2:30:00 8/18/2019 1:00 AM Chn
# 86 user3 127.0.0.1 dell 8/18/2019 2:00 PM
# 21 user4 127.0.0.4 apple 30:00 8/17/2019 1:00 PM Eng
$cols = 0,19,38,59,81,97,120,123 # fake extra column at the end, assumes all rows are that wide
$firstline = get-content columns.txt | select -first 1
$headers = for ($i = 0; $i -lt $cols.count - 1; $i++) {
$firstline.substring($cols[$i], $cols[$i+1]-$cols[$i]).trim()
}
# string Substring(int startIndex, int length)
$lines = Get-Content columns.txt | select -skip 1
$lines | ForEach {
$hash = [ordered]#{}
for ($i = 0; $i -lt $headers.length; $i++) {
$hash += #{$headers[$i] = $_.substring($cols[$i], $cols[$i+1]-$cols[$i]).trim()}
}
[pscustomobject]$hash
}
Output:
PS /Users/js/foo> ./columns | ft
Customer ID Client Name Computer Name Computer Brand Duration Connection Time Lan
----------- ----------- ------------- -------------- -------- --------------- ---
123 first last 127.0.0.1 lenovo 10:00 8/18/2019 6:00 PM Eng
1 lastname 127.0.0.2 apple 2:30:00 8/18/2019 1:00 AM Chn
86 user3 127.0.0.1 dell 8/18/2019 2:00 PM
21 user4 127.0.0.4 apple 30:00 8/17/2019 1:00 PM Eng
I think you have a couple of requirements here. I'm going to describe one way to do it using a generic 'for loop' and regular expression - something you can play with and tweak to your needs. There are better ways of doing this (Powershell shortcuts), but based on the way you asked I'm going to assume that understanding is your goal, so this code should serve well if you have a background in any programming language. Hope this helps!
# Here is your data formatted in an array. Missing values are just empty fields.
# You could have fewer or more fields, but I've broken up your data into nine fields
# (0-8 when counting elements in an array)
# Customer ID, FName, LName, ComputerHostName, Brand, Duration, ConnectionDate, ConnectionTime, Lang
$myarray = #(
('123', 'firstname', 'lastname', '127.0.0.1', 'lenovo', '10:00', '8/18/2019', '6:00 PM', 'Eng'),
('1', 'lastnam', '', '127.0.0.2', 'apple', '2:30:00', '8/18/2019', '1:00 AM', 'Chn'),
('86', 'user3', '', '127.0.0.1', 'dell', '04:33', '8/18/2019', '2:00 PM', ''),
('21', 'user4', '', '127.0.0.4', 'apple', '30:00', '8/17/2019', '1:00 PM', 'Eng')
)
# This is a generic for loop that prints the ComputerHostName, which is the 4th column.
# The 4th column is column #3 if counting from zero (0,1,2,3)
# I'm using a regular expression to match duration above 30 minutes with the '-match' operator
for ( $i = 0; $i -lt $myarray.Length; $i++ ) {
if ( $myarray[$i][5] -match "[3-5][0-9]:[0-9][0-9]$" ){
"$($myarray[$i][5]) - $($myarray[$i][3])"
}
}
Printed Result:
2:30:00 - 127.0.0.2
30:00 - 127.0.0.4

The months difference between dates using PowerShell

I need to have AD User Account Expiration Date and now how many months and date its remain until will be disabled.
I tried the code under but I am getting in the months 1 and I have less than one month I would like to have answer like 0 month and 27 days
$StartDate (DateNow)
2019-08-29 00:00:00
AccountExpirationDate
---------------------
2019-09-26 00:00:00
$ExpirDate = Get-ADUser test111 -Properties AccountExpirationDate | select AccountExpirationDate
AccountExpirationDate
---------------------
2019-09-26 00:00:00
$EndDate= $ExpirDate.AccountExpirationDate
2019-09-26 00:00:00
$StartDate = (GET-DATE)
2019-08-29 00:00:00
NEW-TIMESPAN –Start $StartDate –End $EndDate
Days : 27
Hours : 10
Minutes : 29
Seconds : 56
$monthdiff = $EndDate.month - $StartDate.month + (($EndDate.Year - $StartDate.year) * 12)
1
(Here I got the number 1 but I have less than one month)
I found no easy way to do this in PowerShell (as TimeSpan doesn't support month counting), hence I ended up with the following. Starting with the years of the two dates, take their difference and course correct if the start day hasn't passed in the current year. Then do the same with the months:
$StartDate = [DateTime]'2021-01-23'
$today = Get-Date
$daydiff = New-TimeSpan -Start $StartDate -End $today
$yeardiff = $today.year - $StartDate.year
If($yeardiff -gt 0 -And $StartDate.month -gt $today.month
-And $StartDate.day -gt $today.day) {
$yeardiff = $yeardiff -1
}
$monthdiff = $today.month - $StartDate.month + ($yeardiff * 12)
If($StartDate.day -gt $today.day) { $monthdiff = $monthdiff -1 }
Write-Host "$($daydiff.days) days | $($monthdiff) months"
Simplest solution I could work out.
I get the number of months between the start date and the monthly anniversary day in the current month. Then adjust:
$start_date = Get-Date '2022-02-27 21:00'
$end_date = Get-Date
# get the monthly anniversary of the $start_date in the current month
$this_month_anniversary = Get-Date ('{0}-{1}-{2} {3:d2}:{4:d2}' -f $end_date.Year, $end_date.Month, $start_date.Day, $start_date.Hour, $start_date.Minute)
# get the number days in the month, so we can get a denominator when figuring the percent of the way we are towards the next anniversay.
# Which month? If we're past this month's anniversary use the current month. If we haven't reached it yet, use the previous month
if ($end_date -gt $this_month_anniversary) {
$days_in_month = [DateTime]::DaysInMonth($end_date.Year, $end_date.Month)
} else {
$last_month = (Get-Date ('{0}-{1}-01' -f $end_date.Year, $end_date.Month)).AddDays(-1)
$days_in_month = [DateTime]::DaysInMonth($last_month.Year, $last_month.Month)
}
# get months between the start date and this month's anniversay, then
# adjust for the current month, this will be negative if the anniversay hasn't occured yet, otherwise positive
($this_month_anniversary.Month - $start_date.month + (($this_month_anniversary.Year - $start_date.year) * 12) +
(New-TimeSpan -Start $this_month_anniversary -End $end_date).TotalDays / $days_in_month)
I believe this is what you want, or can be tweaked to achieve it relatively easily.
$today = Get-Date;
$endOfYearDate = "12/31/$($today.Year)";
$endOfYear = Get-Date($endOfYearDate);
$monthsLeftInTheYear = ($endOfYear.Month - $today.Month);
$daysLeftInTheYear = ($endOfYear - $today);
$daysLefInTheYear.Days;
You can just do normal arithmetic on dates, but if there are no months, it will return $null not 0.
$today - date
$ExpirDate = Get-ADUser test111 -Properties AccountExpirationDate | select AccountExpirationDate
$diffday = $today - $expirDate
$diffday.days
$diffday.months
if ($diffday.months -eq $null)
{
$Diffday.months =0
}

Split a logfile

I have a log file with this:
Wed Oct 17 05:39:27 2018 : Resource = 'test04' cstep= 'titi04' time =18.751s
Wed Oct 17 05:40:31 2018 : Resource = 'test05' cstep= 'titi05' time =58.407s
Wed Oct 17 05:41:31 2018 : Resource = 'test06' cstep= 'titi06' time =3.400s
Wed Oct 17 05:42:31 2018 : Resource = 'test07' cstep= 'titi07' time =4.402s
I want split and want only the values greater than 5:
18.751
58.407
My script is in PowerShell and collects all values, not just values greater than 5:
$list = Get-Content "C:\Users\Desktop\slow_trans\log_file.txt"
$results = foreach ($line in $list) {
$line.Split('=')[3].Trim().TrimEnd('s')
}
$results
Results are
18.751
58.407
3.400
4.402
I want only
3.400
4.402
Changing the requirements on the fly is normally a no go,
so you don't deserve it.
Also the wording Superior 5 reminds me at a previous question from another user account..
Nevertheless here a script with a single pipe and datetime conversion.
## Q:\Test\2018\11\06\SO_53170145.ps1
Get-Content .\logfile.txt |
Where-Object {$_ -match '^(.*?) : .*time =([0-9\.]+)s'}|
Select-Object #{n='DT';e={([datetime]::ParseExact($Matches[1],'ddd MMM dd HH:mm:ss yyyy',[cultureinfo]::InvariantCulture).ToString('yyyy-MM-dd HH:mm:ss'))}},
#{n='val';e={[double]$Matches[2]}} |
Where-Object val -le 5
Sample output (decimal comma due to my German locale)
DT val
-- ---
2018-10-17 05:41:31 3,4
2018-10-17 05:42:31 4,402
the following casts the selected string as double and then returns only those which are less than 5
$results = Foreach ($line in $list) {
$val = [double]$line.Split('=')[3].Trim().TrimEnd('s')
if($val -lt 5) {
$val
}
}
Select-String is one option:
(Select-String -Path "TargetLog.txt" -Pattern ".*(?<time>\d+\.\d+)s").Matches |
ForEach-Object {
if([double]$_.Groups['time'].Value -lt 5.0) {$_.Value}
}
This will output the entire matching line:
Wed Oct 17 05:41:31 2018 : Resource = 'test06' cstep= 'titi06' time =3.400s
Wed Oct 17 05:42:31 2018 : Resource = 'test07' cstep= 'titi07' time =4.402s
If you only want the number from each line, change the if block to this:
{$_.Groups['time'].Value}

Get the last day of a month on powershell

I'm trying to put in a .txt file the first day and the last day of the months using PowerShell.
In the exemple below i was trying to get the first and the last day of July, but i'm just getting the first day. The second line of the script isn't working.
#PowerShell "(Get-Date).AddMonths(1).ToString('01/MM/yyyy')" >>dates.txt
$LastDayInMonthString = "$($(get-date).AddMonths(1).ToString("dd/mm/yyy"))$LastDayInMonth" >>dates.txt
Someone can say me what is wrong?
I wanted a .txt file like it: 01/07/2018, 31/07/2018.
The first line write the first day of next month,
and second line write the last day of that month.
Much simpler solution is to call into the DaysInMonth function
[DateTime]::DaysInMonth(2018, 11)
For the current month that would look like:
$today = get-date
$lastDay = [DateTime]::DaysInMonth($today.Year, $today.Month)
$firstDate = [DateTime]::new($today.Year, $today.Month, 1)
$lastDate = [DateTime]::new($today.Year, $today.Month, $lastDay)
$firstDate
$lastDate
This also works around any hindering daylight savings changes and other weird things that can happen with timezones etc.
Or if pure strings are all you need:
(get-date -Format "yyyy/MM") + "/1"
(get-date -Format "yyyy/MM") + "/" + [DateTime]::DaysInMonth((get-date).Year, (get-date).Month)
An easy way is to take the last day of the previous year and add 1..12 months to it:
1..12 | % { (New-Object DateTime(2017,12,31)).AddMonths($_) }
Output will be in the user's date/time format, in my case Dutch:
woensdag 31 januari 2018 00:00:00
woensdag 28 februari 2018 00:00:00
zaterdag 31 maart 2018 00:00:00
maandag 30 april 2018 00:00:00
donderdag 31 mei 2018 00:00:00
zaterdag 30 juni 2018 00:00:00
dinsdag 31 juli 2018 00:00:00
vrijdag 31 augustus 2018 00:00:00
zondag 30 september 2018 00:00:00
woensdag 31 oktober 2018 00:00:00
vrijdag 30 november 2018 00:00:00
maandag 31 december 2018 00:00:00
If required you can format it as you need it, e.g.
1..12 | % { (New-Object DateTime(2017,12,31)).AddMonths($_).ToString("yyyyMMdd") }
20180131
20180228
20180331
20180430
20180531
20180630
20180731
20180831
20180930
20181031
20181130
20181231
This seems simple enough
$firstDate = [DateTime]::new($reportDate.Year, $reportDate.Month, 1)
$lastDate=$firstDate.AddMonths(1).AddDays(-1)
Edit removed the for only date unneccessary time adjustments
In PowerShell to get the first day and last of next month
$CIGB = New-Object System.Globalization.CultureInfo('en-GB')
'{0}, {1}' -f (Get-Date -Day 1).AddMonths(1).ToString('d',$CIGB),
(Get-Date -Day 1).AddMonths(2).AddDays(-1).ToString('d',$CIGB)|sc dates.txt
The $CIGB is neccessary for me because my local date separator overrides the /
If your short date format 'd' returns dd/MM/yyyy the first line and the ,$CIGB can be removed.
01/07/2018, 31/07/2018
This can be wrapped in a single (albeit quite long) line.
powershell -nop -c "$CIGB=New-Object System.Globalization.CultureInfo('en-GB');'{0}, {1}' -f (Get-Date -Day 1).AddMonths(1).ToString('d',$CIGB),(Get-Date -Day 1).AddMonths(2).AddDays(-1).ToString('d',$CIGB)">>dates.txt
> type dates.txt
01/07/2018, 31/07/2018
Strings can be passed to Get-Date. The -Format parameter also accepts strings/variables.
# Get the previous month
$LastMonth = (Get-Date).AddMonths(-1)
# Format the first day of the month
$FirstDayOfMonth = $LastMonth | Get-Date -Format "yyyy/MM/01"
# Calculate the number of days (last day) in the month
$LastDay = [DateTime]::DaysInMonth($LastMonth.Year, $LastMonth.Month)
# Use it in the format parameter
$LastDayOfMonth = $LastMonth | Get-Date -Format "yyyy/MM/$lastDay"

Powershell - Search and output line by line

UPDATE
I have got a log file of 1000 lines containing some reference.
Time Reference Date of start Date of end
12:00 AT001 13 November 2011 15 November 2011
13:00 AT038 15 December 2012 17 December 2012
14:00 AT076 17 January 2013 19 January 2013
$ref1 = AT038
Basically, I want to parse the log file and have an output (line by line) for $ref1 such as :
Time : 13h
Reference : AT038
Date of start : 15 December 2012
Date of end : 17 December 2012
Thanks in advance
try:
$ref1 = "AT038"
$csv = Import-Csv .\myfile.txt -Delimiter ' '#Import file as CSV with space as delimiter
$csv | ? { $_.reference -EQ $ref1 } | FL #Piping each line of CSV to where-object cmdlet, filtering only line where value of column reference is equal to $ref1 variable value. Piping the result of the filtering to file-list to have output as requested in OP.
Code added after requisite are changed in OP:
$ref1 = "AT038"
$txt = gc .\myfile.txt
$txt2 = $txt | % { $b = $_ -split ' '; "$($b[0]) $($b[1]) $($b[2])_$($b[3])_$($b[4]) $($b[5])_$($b[6])_$($b[7])" }
$csv = convertfrom-csv -InputObject $txt2 -Delimiter ' '
$csv | ? { $_.reference -EQ $ref1 } | FL
How about this:
Get-Content SourceFileName.txt |
% { ($_ -Replace '(\d{2}):\d{2} (\w{2}\d{3})', 'Time : $1h|Reference : $2').Split('|')} |
Out-File TargetFileName.txt
Here is my revised version:
$regex = '(\d{2}):\d{2} (\w{2}\d{3}) (\d{2} \b\w+\b \d{4}) (\d{2} \b\w+\b \d{4})'
$replace = 'Time : $1h|Reference : $2|Date of start : $3|Date of end : $4'
Get-Content SourceFileName.txt |
% { ($_ -Replace $regex, $replace).Split('|')} |
Out-File TargetFileName.txt