Piping error with extra line and does not give a header - powershell

0001;Third Week;Every Monday 12am-2am
002;Third Week;Every Tuesday 8pm-10pm
003;Third Week;Every Monday 12am-2am
#Get the number of lines in a CSV file
$Lines = (Import-Csv "C:\MM1.csv").count
#Import the CSV file
$a = #(Import-CSV "C:\MM1.csv")
$month = Get-Date -Format MMMM
#loop around the end of the file
for ($i=0; $i -le $lines; $i++) {
$Servername = $a[$i].ServerName
$week = $a[$i].Week
$dayweekString = [String]$a[$i].DayTime
# This will help in getting the Day of the WeekDay String
$dayweekString = ($dayweekString -split "\s+",(3))[(1)]
#This will find the time Ex 2am or 8pm, it can be any time
$DayNew = if ($Day -match "\d{1,2}Travelm") {$Matches[0]}
#Format for Maintenance mode which can be fed into the SCOM MM Script.
$MaintenanceTime = get-date "$DayNew $month.$($_.$dayweekString).$year"
write-host $Servername, $MaintenanceTime
#Store all my data while in a for each loop
New-Object -TypeName PSObject -Property #{
ServerNameNew = $Servername
TimeStamp = $MaintenanceTime
} | Select-Object ServerNameNew,TimeStamp
} | Export-Csv -Path "C:\MM3.csv" -Append
# Error as extra Pipe
I am not able to pipe the output while in a loop with a header. Says Extra pipe and writes a extra line of TimeStamp Variable to the line.

You are trying to pipe the result of the for statement to another command. That's not supported. If you want to do that, you need to use a subexpression around the statement:
$( for(){} ) | ...
(The reason is that pipelines need an expression as their first element. And for is not an expression, it's a statement.)
However, in your case I'd replace the for with a simple pipeline iterating over the array, like this:
Import-CSV "C:\MM1.csv" | ForEach-Object {
$Servername = $_.ServerName
$week = $_.Week
$dayweekString = [String]$_.DayTime
...
}
Generally there is very rarely a reason to use explicit looping constructs in PowerShell. It leads to code that's awkward at best (because it resembles converted C# or VBScript code) and horrible at worst. Just don't do it.

Related

how to read time from file and perform minus operation on hours using powershell

I have created a text file that contains id and time[24hr format] that is going to trigger one operation at the mentioned time.
I want to read both the values and minus 2 hrs from mentioned hr.
I tried, but I am not sure how to read input as time format(24hr notation HH:MM) and minus 2 hrs from that and also the scenario like 01:30 - 2hr = 23.30 (previous date)
$lines = Get-Content C:\wamp64\www\schedule_daily_task.txt | Where {$_ -notmatch '^\s+$'}
foreach ($line in $lines) {
$fields = $line -split '\s+'
$id = $fields[0]
$time = $fields[1]
#code here to change the format of $time to time format and perform minus oper
#01:30 - 2hr = 23.30 (previous date)
}
For date and time arithmetics, use .Net's DateTime class. It has method AddHours() which, when passed negative values, subtracts hours. Calculating new datetime to yesterday is quite straight-forward. Like so,
# First, let's get today's date with specific time and minute
$hh = ($fields[1] -split ':')[0]
$mm = ($fields[1] -split ':')[1]
$dd = get-date -date $(get-date).date -hour $hh -minute $mm
# Add -2 hours
$dd.AddHours(-2)
I would use switch -Regex -File for this to read the file line-by-line and get the values from the lines you are after using regex.
Advantage is that this is crazy fast aswell.
$result = switch -regex -File 'C:\wamp64\www\schedule_daily_task.txt' {
'^(\d+)\s+(\d{2}:\d{2})' {
$id = $Matches[1]
$h, $m = $Matches[2] -split ':'
$time = '{0:HH:mm}' -f (Get-Date -Hour $h -Minute $m -Second 0).AddHours(-2)
# output the id and updated time
"$id $time"
}
default {
# output any other line you may have in the file. You can of course
# also ignore these if they are unwanted in the output. In that case
# simply comment out this whole 'default' block
$_
}
}
# output to (new) file and because of '-PassThru' also on screen
$result | Set-Content -Path 'C:\wamp64\www\schedule_daily_task_minus_2.txt' -PassThru
Example input file:
2603961 01:30
1234567 11:59
9876543 23:30
Example output file:
2603961 23:30
1234567 09:59
9876543 21:30

PowerShell script efficiency advice

I have a telephony .csv with compiled data from January 2020 and some days of February, each row has the date and time spent on each status, since someone uses different status over the day the file has one row for each status, my script is supposed to go through the file, find the minimum date and then start saving on new files all the data for the same day, so I'll end with one file for 01-01-2020, 02-01-2020 and so on, but it has 15 hours running and it's still at 1/22.
The column I'm using for the dates is called "DateFull" and this is the script
write-host "opening file"
$AT= import-csv “C:\Users\xxxxxx\Desktop\SignOnOff_20200101_20200204.csv”
write-host "parsing and sorting file"
$go= $AT| ForEach-Object {
$_.DateFull= (Get-Date $_.DateFull).ToString("M/d/yyyy")
$_
}
Write-Host "prep day"
$min = $AT | Measure-Object -Property Datefull -Minimum
Write-Host $min
$dateString = [datetime] $min.Minimum
Write-host $datestring
write-host "Setup dates"
$start = $DateString - $today
$start = $start.Days
For ($i=$start; $i -lt 0; $i++) {
$date = get-date
$loaddate = $date.AddDays($i)
$DateStr = $loadDate.ToString("M/d/yyyy")
$now = Get-Date -Format HH:mm:ss
write-host $datestr " " $now
#Install-Module ImportExcel #optional import if you dont have the module already
$Check = $at | where {$_.'DateFull' -eq $datestr}
write-host $check.count
if ($check.count -eq 0 ){}
else {$AT | where {$_.'DateFull' -eq $datestr} | Export-Csv "C:\Users\xxxxx\Desktop\signonoff\SignOnOff_$(get-date (get-date).addDays($i) -f yyyyMMdd).csv" -NoTypeInformation}
}
$at = ''
The first loop doesn't make much sense. It loops through CSV contents and converts each row's date into different a format. Afterwards, $go is never used.
$go= $AT| ForEach-Object {
$_.DateFull= (Get-Date $_.DateFull).ToString("M/d/yyyy")
$_
}
Later, there is an attempt to calculate a value from uninitialized a variable. $today is never defined.
$start = $DateString - $today
It looks, however, like you'd like to calculate, in days, how old eldest record is.
Then there's a loop that counts from negative days to zero. During each iteration, the whole CSV is searched:
$Check = $at | where {$_.'DateFull' -eq $datestr}
If there are 30 days and 15 000 rows, there are 30*15000 = 450 000 iterations. This has complexity of O(n^2), which means runtime will go sky high for even relative small number of days and rows.
The next part is that the same array is processed again:
else {$AT | where {$_.'DateFull' -eq $datestr
Well, the search condition is exactly the same, but now results are sent to a file. This has a side effect of doubling your work. Still, O(2n^2) => O(n^2), so at least the runtime isn't growing in cubic or worse.
As for how to fix this, there are a few things. If you sort the CSV based on date, it can be processed afterwards in just a single run.
$at = $at | sort -Property datefull
Then, iterate each row. Since the rows are in ascending order, the first is the oldest. For each row, check if date has changed. If not, add it to buffer. If it has, save the old buffer and create a new one.
The sample doesn't convert file names in yyyyMMdd format, and it assumes there are only two columns foo and datefull like so,
$sb = new-object text.stringbuilder
# What's the first date?
$current = $at[0]
# Loop through sorted data
for($i = 0; $i -lt $at.Count; ++$i) {
# Are we on next date?
if ($at[$i].DateFull -gt $current.datefull) {
# Save the buffer
$file = $("c:\temp\OnOff_{0}.csv" -f ($current.datefull -replace '/', '.') )
set-content $file $sb.tostring()
# Pick the current date
$current = $at[$i]
# Create new buffer and save data there
$sb = new-object text.stringbuilder
[void]$sb.AppendLine(("{0},{1}" -f $at[$i].foo, $at[$i].datefull))
} else {
[void]$sb.AppendLine(("{0},{1}" -f $at[$i].foo, $at[$i].datefull))
}
}
# Save the final buffer
$file = $("c:\temp\OnOff_{0}.csv" -f ($current.datefull -replace '/', '.') )
set-content $file $sb.tostring()

Finding value in a CSV file and comparing with today's date

I'm trying to get a value from a CSV file.
If today's date = DateInCSVFile give the "key" value.
Keys.csv
Guest,Key
1-Jun,OIOMY-ZFILZ
2-Jun,LSSJC-PDEUL
3-Jun,MQNVJ-TETLV
4-Jun,HCJIJ-ECVPY
5-Jun,SPACR-AJSLU
6-Jun,MEURS-UQTVX
Code:
$today = Get-Date -format dd-MMM
$keys = import-csv c:\office\keys.csv -Header #(1..2)
$data = $keys | ? { $_.1 -match $today}
Write-Host $data.2
I tried the foreach and if commands. Nothing worked.
I can think of a couple of options. If you want something quick and dirty, try:
$stuff = Import-Csv -Path .\stuff.csv
foreach ($thing in $stuff) {
if ( $thing.Guest -eq $(Get-date -Format 'd-MMM') ) {
Write-Output $thing.Key
}
}
I import the CSV file's contents to a variable. I iterate over each line. If the day in Guest matches the current day, I output the key
The only problem with your code is your date format, dd-MMM, as LotPings observes:
It creates 0-left-padded numbers for single-digit days such as 6, whereas the dates in the CSV have no such padding.
Thus, changing Get-Date -format dd-MMM to Get-Date -format d-MMM (just d instead of dd) should fix your problem.
However, given that you're reading the entire file into memory anyway, you can optimize the command to (PSv4+):
$today = Get-Date -Format d-MMM
(Import-Csv c:\office\keys.csv).Where({ $_.Guest -eq $today }).Key
Also note that the purpose of -match is to perform regular-expression-based matching, not (case-insensitive) string equality; use -eq for the latter.

Compare current date to date string in a file using powershell

I am writing some PS scripts to log times into a text file, login.txt, using the following code:
$logdir = "C:\FOLDER"
$logfile = "$logdir\LastLogin.txt"
$user = $env:USERNAME
$date = Get-Date -Format "dd-MM-yyyy"
if (!(Test-Path $logdir)){New-Item -ItemType Directory $logdir}else{}
if (!(Test-Path $logfile)){New-Item $logfile}else{}
if (Get-Content $logfile | Select-String $user -Quiet){write-host "exists"}else{"$user - $date" | Add-Content -path $logfile}
(Get-Content $logfile) | Foreach-Object {$_ -replace "$user.+$", "$user - $date"; } | Set-Content $logfile
This creates an entry in the text file like:
UserName - 01-01-1999
Using Powershell, I want to read the text file, compare the date, 01-01-1999, in the text file to the current date and if more than 30 days difference, extract the UserName to a variable to be used later in the script.
I would really appreciate any hints as to how I could do the following:
Compare the date in the text file to the current date.
If difference is more than 30 days, pick up UserName as a variable.
I would really appreciate any advice.
Checking all dates in the file with the help of a RegEx with named capture groups.
$logdir = "C:\FOLDER"
$logfile = Join-Path $logdir "LastLogin.txt"
$Days = -30
$Expires = (Get-Date).AddDays($Days)
Get-Content $logfile | ForEach-Object {
if ($_ -match "(?<User>[^ ]+) - (?<LastLogin>[0-9\-]+)") {
$LastLogin = [datetime]::ParseExact($Matches.LastLogin,"dd-MM-yyyy",$Null)
if ( $Expires -gt $LastLogin ) {
"{0} last login {1} is {2:0} days ago" -F $Matches.User, $Matches.LastLogin,
(New-TimeSpan -Start $LastLogin -End (Get-Date) ).TotalDays
}
}
}
Sample output
username last login 31-12-1999 is 6690 days ago
There is a way of doing that using regex (Regular Expressions). I will assume that the username which you get in your text file is .(dot) separated. For example, username looks like john.doe or jason.smith etc. And the entry in your text file looks like john.doe - 01-01-1999 or jason.smith - 02-02-1999. Keeping these things in mind our approach would be -
Using a regex we would get the username and date entry into a single variable.
Next up, we will split the pattern we have got in step 1 into two parts i.e. the username part and the date part.
Next we take the date part and if the difference is more than 30 days, we would take the other part (username) and store it in a variable.
So the code would look something like this -
$arr = #() #defining an array to store the username with date
$pattern = "[a-z]*[.][a-z]*\s[-]\s[\d]{2}[-][\d]{2}[-][\d]{4}" #Regex pattern to match entires like "john.doe - 01-01-1999"
Get-Content $logfile | Foreach {if ([Regex]::IsMatch($_, $pattern)) {
$arr += [Regex]::Match($_, $pattern)
}
}
$arr | Foreach {$_.Value} #Storing the matched pattern in $arr
$UserNamewithDate = $arr.value -split ('\s[-]\s') #step 2 - Storing the username and date into a variable.
$array = #() #Defining the array that would store the final usernames based on the time difference.
for($i = 1; $i -lt $UserNamewithDate.Length;)
{
$datepart = [Datetime]$UserNamewithDate[$i] #Casting the date part to [datetime] format
$CurrentDate = Get-Date
$diff = $CurrentDate - $datepart
if ($diff.Days -gt 30)
{
$array += $UserNamewithDate[$i -1] #If the difference between current date and the date received from the log is greater than 30 days, then store the corresponding username in $array
}
$i = $i + 2
}
Now you can access the usernames like $array[0], $array[1] and so on. Hope that helps!
NOTE - The regex pattern will change as per the format your usernames are defined. Here is a regex library which might turn out to be helpful.

Parse out date from filename and sort by date

I have a series of files named as such in a folder:
- myFile201801010703.file
I'm trying to parse out the yyyymmdd portion of each filename in the folder and sort them based on the date into an array.
So if I had the following files:
myFile201801200000.file (01/20/2018)
myFile201800100000.file (01/01/2018)
myFile201801100000.file (01/10/2018)
It would sort them into an array as such:
myFile201800100000.file (01/01/2018)
myFile201801100000.file (01/10/2018)
myFile201801200000.file (01/20/2018)
I have a process that works for file with timestamps included in the name, though have been unable to tweak it for work with only a date:
# RegEx pattern to parse the timestamps
$Pattern = '(\d{4})(\d{2})(\d{2})*\' + ".fileExtension"
$FilesList = New-Object System.Collections.ArrayList
$Temp = New-Object System.Collections.ArrayList
Get-ChildItem $SourceFolder | ForEach {
if ($_.Name -match $Pattern) {
Write-Verbose "Add $($_.Name)" -Verbose
$Date = $Matches[2],$Matches[3],$Matches[1] -join '/'
$Time = $Matches[4..6] -join ':'
[void]$Temp.Add(
(New-Object PSObject -Property #{
Date = [datetime]"$($Date) $($Time)" #If I comment out $($Time)it doesn't work.
File = $_
}
))
}
}
} catch {
Write-Host "`n*** $Error ***`n"
}
# Sort the files by the parsed timestamp and add to $FilesList
$FilesList.AddRange(#($Temp | Sort Date | Select -Expand File))
# Clear out the temp collection
$Temp.Clear()
The two lines in particular that I think might be culprit are:
$Time = $Matches[4..6] -join ':' Since I'm not parsing any time
Date = [datetime]"$($Date) $($Time)" Again, no time is parsed. Can't change the type to date either it seems?
With this format:
myFileYYYYMMddHHmm.file
the individual parts of the date and time is already arranged from largest (the year) to smallest (the minute) - this makes the string sortable!
Only thing we need to do is grab the last 12 digits of the file name before the extension:
$SortedArray = Get-ChildItem *.file |Sort-Object {$_.BaseName -replace '^.*(\d{12})$','$1'}
The regex pattern used:
^.*(\d{12})$
Can be broken down as follows:
^ # start of string
.* # any character, 0 or more times
( # capture group
\d{12} # any digit, 12 times
) # end of capture group
$ # end of string
The regex engine will expand $1 in the substitution string to "capture group #1", ie. the 12 digits we picked up at the end.