Last repetition in csv - powershell

I have a csv input file like:
date;time;pc;state
25.01.2017;10:30:57;pc1;inactive
25.01.2017;10:35:57;pc2;active
25.01.2017;10:37:34;pc1;active
25.01.2017;10:38:35;pc3;inactive
25.01.2017;10:39:20;pc1;inactive
25.01.2017;10:42:10;pc2;inactive
25.01.2017;10:42:10;pc3;active
So, i need to show only last pc repetition with inactive state and show the difference between time and current time.
So result must be:
pc1 inactive from 10:39:20
pc2 inactive from 10:42:10
Do not know how to realize it in powershell. Need help :)

Here you go, at the moment it only outputs it via Write-Host but you should be able to rebuild it to do with the data what you want:
$csv = import-csv .\test.csv -Delimiter ";"
$pcs = $csv.pc | select -Unique
foreach($pc in $pcs) {
#get all entries for current pc and order by date/time, then select newest entry
$le = $csv | where {$_.pc -eq $pc} | sort date,time -Descending | select -First 1
#if last state is inactive
if($le.state -eq "inactive"){
$writedate = get-date -Date $le.date -Hour $le.time.Substring(0,2) -Minute $le.time.Substring(3,2) -Second $le.time.Substring(6,2)
$td = (get-date) - $writedate
write-host "time difference for $pc : $($td.Days) days $($td.Hours) hours, $($td.Minutes) minutes, $($td.Seconds) seconds"
}
}
If your time field is not allways in HH:MM:DD you will have to change the script to accomodate that

Two solutions, a short one taking the question literal:
$csv = import-csv .\SO_41941150.csv -Delimiter ";"
$csv|group pc|%{$_.group|select -last 1|? state -eq 'inactive'|%{"$($_.pc) inactive from $($_.time)"}}
With the exact output:
pc1 inactive from 10:39:20
pc2 inactive from 10:42:10
A longer one converting date and time to datetime using ::ParseExact and giving elapsed TotalSeconds/Minutes as a result.
$csv = import-csv .\SO_41941150.csv -Delimiter ";"
$csv | Group-Object pc | Foreach-Object {
$_.Group | Select-Object -last 1 | Where-Object state -eq 'inactive'|
ForEach-Object {
$DT = [datetime]::ParseExact("$($_.date) $($_.time)","dd.MM.yyyy HH:mm:ss",$null)
$Secs = [int]((get-date) - $DT).TotalSeconds
$mins = [int]((get-date) - $DT).TotalMinutes
"$($_.pc) inactive since $Mins minutes or $Secs seconds"
}
}
Output:
pc1 inactive since 9160 minutes or 549578 seconds
pc2 inactive since 9157 minutes or 549408 seconds

Related

Edit csv column

I have a csv with 3 columns.
Date Time Event
12/19/2021 3:00pm Low
12/19/2021 1:30pm Low
12/20/2021 3:00pm Low
I'm trying to subtract 5 hours from each row.
How can I keep each object in a datetime format?
$time0 = Import-Csv $Tempdest | Select 'Time'
$timeEst = $time0 | ForEach-Object {
Write-Host $_.
([datetime]$_).AddHours(-5)
}
By looking at the provided screenshot this should work, it would be updating the imported CSV in memory. If it doesn't work it's best if you can share with us the CSV as plain text.
$csv = Import-Csv path/to/csv.csv
$csv | ForEach-Object {
$date = '{0} {1}' -f $_.Date, $_.Time -as [datetime]
$date = $date.AddHours(-5)
$_.Date = $date.ToShortDateString()
$_.Time = $date.ToShortTimeString()
}
$csv | Format-Table

ForEach-Object changing result of SearchUnifiedAuditLog

I have a powershell script to retrieve the most recent login for guest users. It works fine.
(Based on https://gallery.technet.microsoft.com/office/Get-Guest-User-Last-Login-39f8237e)
$startDate = "{0:yyyy-MM-dd}" -f (get-date).AddDays(-365) #look 1 year back (not sure what the maximum is, but 1 year seems to work)
$endDate = "{0:yyyy-MM-dd}" -f (get-date) #current date.
$externalUserExtention = "*#EXT#*"
$filePath="c:\temp\guest_user_last_logins.txt"
function logtoText($filePath, $msg) {
$msg >> $filepath;
}
function find_last_login_date($user) {
$lastLoginDate = Search-UnifiedAuditLog -UserIds $user.UserPrincipalName -StartDate $startDate -EndDate $endDate| Foreach-Object {$_.CreationDate = [DateTime]$_.CreationDate; $_} | Group-Object UserIds | Foreach-Object {$_.Group | Sort-Object CreationDate | Select-Object -Last 1} | Select CreationDate
Write-Host "User " $user.UserPrincipalName "| Last Login Date -" $lastLoginDate.CreationDate
logtoText $filePath ($user.UserPrincipalName + "," + $lastLoginDate.CreationDate)
Write-Output $user.UserPrincipalName
}
Clear-Content $filePath
logtoText $filePath ('Username', 'Last Login DateTime')
#Get All External Users
$allExternalUsers = Get-MsolUser -All | Where-Object -FilterScript { $_.UserPrincipalName -Like $externalUserExtention }
ForEach($externalUser in $allExternalUsers) {
find_last_login_date($externalUser)
}
The output is as expected:
User user1#external.com#EXT##tenant.onmicrosoft.com | Last Login Date -
user1#external.com#EXT##tenant.onmicrosoft.com
User user2#external.com#EXT##tenant.onmicrosoft.com | Last Login Date - 11/04/2020 12:02:12
user2#external.com#EXT##tenant.onmicrosoft.com
...
User userNNNN#external.com#EXT##tenant.onmicrosoft.com | Last Login Date - 12/04/2020 14:22:25
userNNNN#external.com#EXT##tenant.onmicrosoft.com
...
The extra Write-Output inside find_last_login_date is just their for debugging this.
Now when I change it to ForEach-Object it does something weird. Probably it makes sense to the experts, but not to me :-)
Replace
ForEach($externalUser in $allExternalUsers) {
find_last_login_date($externalUser)
}
With
ForEach-Object -InputObject $allExternalUsers {
find_last_login_date($_)
}
And the output becomes
User userX#external.com#EXT##tenant.onmicrosoft.com | Last Login Date - 10/01/2021 14:05:36 05/01/2021 20:48:45 05/01/2021 16:09:25 04/01/2021 07:36:26 22/12/2020 08:01:07 19/12/2020 10:24:08 18/12/2020 14:20:51 18/12/2020 08:05:55 16/12/2020 17:32:28 14/12/2
020 08:20:56 13/12/2020 07:58:13 11/12/2020 10:58:58 10/12/2020 08:08:28
user1#external.com#EXT##tenant.onmicrosoft.com
user2#external.com#EXT##tenant.onmicrosoft.com
...
userNNNN#external.com#EXT##tenant.onmicrosoft.com
...
So it seems to trigger the script to only look for the last login date once and also the select top 1 fails, etc.
Any idea what I am doing wrong? At first I thought it was about the "nested" ForEach-Object but even with the code to do the search inside a function it keeps doing this.
Is it by default doing things in parallel and do I need to wait for completion of all tasks? Something telse?
I'm looking at ForEach-Object to parallelize the above script as it takes a very long time to run on a large tenant.
From the docs :
When you use the InputObject parameter with ForEach-Object, instead of piping command results to ForEach-Object, the InputObject value is treated as a single object. This is true even if the value is a collection that is the result of a command, such as -InputObject (Get-Process). Because InputObject cannot return individual properties from an array or collection of objects, we recommend that if you use ForEach-Object to perform operations on a collection of objects for those objects that have specific values in defined properties, you use ForEach-Object in the pipeline.
Change
ForEach-Object -InputObject $allExternalUsers {
find_last_login_date($_)
}
into
$allExternalUsers | ForEach-Object {
find_last_login_date($_)
}

updating column value for exported csv powershell

I have following code which is working correctly.
Although I now need to modify the output in one specific column, so I can sort by this column correctly.
Here is my code:
$inputFile = "C:\Data\expPasswords\expPasswords.csv"
$outputFile = "C:\Data\expPasswords\expPasswordsUp.csv"
$result = Import-Csv $inputFile |
Select-Object #{ Name = 'Account'; Expression = { $_.Account } },
#{ Name = 'Days until Expiry'; Expression = { $_.'time until password expires' } },
#{ Name = 'Email address'; Expression = { $_.'email address' } }
# output on screen
$result | Sort-Object -Property 'Days until Expiry' | Format-Table -AutoSize
# output to csv
$result | Sort-Object -Property 'Days until Expiry' | Export-Csv -Path $outputFile -NoTypeInformation
I need to sort by the 'Days until Expiry' column. Although makes it hard when the output is as below:
0 minutes
0 minutes
1 day and 19 hours
1 day and 2 hours
1 day and 20 hours
1 day and 23 hours
13 hours
2 days
20 hours
Basically, what I would like to do is:
- If less than 1 day, make the value: Today
- Remove the hours and minutes blocks.
- So if it is 13 hours, make the value: Today
- If the value is 1 day and 1 hours and 35 minutes, make the value: 1 day
Any assistance will be greatly appreciated. ;-)
Its a shame you should spend time to make some sense out of this rather foolish output, but of course it can be done.
Basically, all you want to do is find out if the string starts with a number followed by the word 'day' or 'days' and cut off all the rest. If this is not the case, the returned value should be 'Today'.
The easiest way to do that I think is by using switch -Regex.
Try
$inputFile = "C:\Data\expPasswords\expPasswords.csv"
$outputFile = "C:\Data\expPasswords\expPasswordsUp.csv"
$result = Import-Csv $inputFile | ForEach-Object {
$daysLeft = switch -Regex ($_.'time until password expires') {
'^(\d+ days?)' { $matches[1] }
default { 'Today' }
}
[PsCustomObject]#{
'Account' = $_.Account
'Days until Expiry' = $daysLeft
'Email address' = $_.'email address'
}
} | Sort-Object -Property 'Days until Expiry'
# output on screen
$result | Format-Table -AutoSize
# output to csv
$result | Export-Csv -Path $outputFile -NoTypeInformation
Regex details:
^ Assert position at the beginning of the string
\d Match a single character that is a “digit” (any decimal number in any Unicode script)
+ Between one and unlimited times, as many times as possible, giving back as needed (greedy)
\ day Match the character string “ day” literally (case sensitive)
s Match the character “s” literally (case sensitive)
? Between zero and one times, as many times as possible, giving back as needed (greedy)
Seeing your comment, I would suggest adding a real DateTime object to sort on.
Something like this:
$today = (Get-Date).Date
$result = Import-Csv 'D:\test.csv' | ForEach-Object {
$expiryString = $_.'time until password expires'
$expiryDate = $today
if ($expiryString -match '(\d+)\s*day') { $expiryDate = $expiryDate.AddDays([int]$matches[1]) }
if ($expiryString -match '(\d+)\s*hour') { $expiryDate = $expiryDate.AddHours([int]$matches[1]) }
if ($expiryString -match '(\d+)\s*minute') { $expiryDate = $expiryDate.AddMinutes([int]$matches[1]) }
if ($expiryString -match '(\d+)\s*second') { $expiryDate = $expiryDate.AddSeconds([int]$matches[1]) }
$daysLeft = if ($expiryDate.Date -eq $today) { 'Today' } else { ($expiryDate - $today).Days}
[PsCustomObject]#{
'Account' = $_.Account
'Email address' = $_.'email address'
'Days until Expiry' = $daysLeft
'Expiration Date' = $expiryDate
}
} | Sort-Object -Property 'Expiration Date'
# output on screen
$result
Output:
Account Email address Days until Expiry Expiration Date
------- ------------- ----------------- ---------------
User1 user1#yourcompany.com Today 6-4-2020 0:00:00
User6 user6#yourcompany.com Today 6-4-2020 0:03:00
User8 user8#yourcompany.com Today 6-4-2020 13:00:00
User4 user4#yourcompany.com Today 6-4-2020 20:00:00
User9 user9#yourcompany.com 1 7-4-2020 2:00:00
User2 user2#yourcompany.com 1 7-4-2020 19:00:00
User5 user5#yourcompany.com 1 7-4-2020 20:00:00
User7 user7#yourcompany.com 1 7-4-2020 23:00:00
User3 user3#yourcompany.com 2 8-4-2020 0:00:00
If you don't want that new property 'Expiration Date' in your output, simply filter it away with:
$result | Select-Object * -ExcludeProperty 'Expiration Date'
I think the following might be of help (you will need to edit some of it, off course):
$Timings = #("0 minutes","0 minutes","1 day and 19 hours","1 day and 2 hours","1 day and 20 hours","1 day and 23 hours","13 hours","2 days","20 hours")
foreach ($Timing in $Timings) {
$Output = $null
if ($Timing -like "* minutes") {$Output = 0}
elseif ($Timing -like "* Day and * hours") {$Output = [int](($Timing).Split(' day')[0])}
elseif ($Timing -like "* hours") {$Output = 0}
else {$Output = [int](($Timing).Split(' day')[0]) }
switch ($Output) {
0 {$Result = "Today"}
1 {$Result = "Tomorrow"}
default {$Result = "Over $Output Days"}
}
Write-Output "$timing ==> $Result"
}
The constrains you defined will likely make it more confusing. I would just convert it to a [TimeSpan] structure which makes it easy to sort:
$Result = ConvertFrom-Csv #'
"Account","Days until Expiry", "Email address"
"Account1","0 minutes", "Name1#gmail.com"
"Account2","1 day and 19 hours","Name2#gmail.com"
"Account3","2 days", "Name3#gmail.com"
"Account4","20 hours", "Name4#gmail.com"
"Account5","1 day and 20 hours","Name5#gmail.com"
"Account6","3 minutes", "Name6#gmail.com"
"Account7","1 day and 23 hours","Name7#gmail.com"
"Account8","13 hours", "Name8#gmail.com"
"Account9","1 day and 2 hours", "Name9#gmail.com"
'#
Function ConvertTo-TimeSpan([String]$String) {
$Days = If ($String -Match '\d+(?=\s*day)') {$Matches[0]} Else {0}
$Hours = If ($String -Match '\d+(?=\s*hour)') {$Matches[0]} Else {0}
$Minutes = If ($String -Match '\d+(?=\s*minute)') {$Matches[0]} Else {0}
$Seconds = If ($String -Match '\d+(?=\s*second)') {$Matches[0]} Else {0}
New-TimeSpan -Days $Days -Hours $Hours -Minutes $Minutes -Seconds $Seconds
}
$Result | Sort #{e = {ConvertTo-TimeSpan $_.'Days until Expiry'}}
Result:
Account Days until Expiry Email address
------- ----------------- -------------
Account1 0 minutes Name1#gmail.com
Account6 3 minutes Name6#gmail.com
Account8 13 hours Name8#gmail.com
Account4 20 hours Name4#gmail.com
Account9 1 day and 2 hours Name9#gmail.com
Account2 1 day and 19 hours Name2#gmail.com
Account5 1 day and 20 hours Name5#gmail.com
Account7 1 day and 23 hours Name7#gmail.com
Account3 2 days Name3#gmail.com

Powershell code to filter login/logout times (id 7001, 42)

I am trying to create code in Powershell that will track the user login/logout times with the id codes of 7001 (login), and 42 (computer goes to sleep), and then export it as a csv.
My current problem is that sometimes the user will login/logout throughout the day, but I just want the earliest login and latest logout so I can track the total hours.
My current code works, but it gets every login/logout events of the day, seen below:
$startDate = (get-date).AddDays(-1)
$FileName = "Y:\Powershell_ " + $startDate.ToString('MMddyy') + ".csv"
$log_time = get-WinEvent -FilterHashtable #{logname='system';id='7001', '42'}
$log_time| Select Id, MachineName, Message, TimeCreated | export-csv $FileName
Thank you in advance
You have to filter by the day you working on first.
Since you can catch the output of a for each loop with a parameter, this is a way you can achieve your goal:
$startDate = (get-date).AddDays(-1).GetDateTimeFormats()[0]
$FileName = "somepath.csv"
$log_time = get-WinEvent -FilterHashtable #{logname='system';id='7001', '42'}
$daily_events = foreach($event in $log_time)
{
if ($event.timecreated.GetDateTimeFormats()[0] -like "*$startDate*"){$event}
}
$daily_events | Select Id, MachineName, Message, TimeCreated | export-csv -Path $Filename
You can add this to track about the first and last while execution:
#The sort is by default from newer to older
$first_event = $daily_events[-1].TimeCreated #last element of the array
$last_event = $daily_events[0].TimeCreated #first element of the array
Write-Host "First event was $first_event in and the last event was in $last_event"
You can use Get-Eventlog to make sure only events from the current day are retrieved. From there:
$Results =[System.Collections.Generic.List[PSObject]]::new()
$Date = (get-date).Date
$FirstLogin = Get-EventLog -LogName System -InstanceId 7001 -After $Date -Before $Date.AddDays(1) | Select -Last 1
$LastLogout = Get-EventLog -LogName System -InstanceId 42 -After $Date -Before $Date.AddDays() -Newest 1
$Results.Add($FirstLogin)
$Results.Add($LastLogout)
$Results | Select Id, MachineName, Message, TimeCreated | export-csv -Path $Filename

Multiple Criteria Matching in PowerShell

Hello PowerShell Scriptwriters,
I got an objective to count rows, based on the multiple criteria matching. My PowerShell script can able to fetch me the end result, but it consumes too much time[when the rows are more, the time it consumes becomes even more]. Is there a way to optimism my existing code? I've shared my code for your reference.
$csvfile = Import-csv "D:\file\filename.csv"
$name_unique = $csvfile | ForEach-Object {$_.Name} | Select-Object -Unique
$region_unique = $csvfile | ForEach-Object {$_."Region Location"} | Select-Object -Unique
$cost_unique = $csvfile | ForEach-Object {$_."Product Cost"} | Select-Object -Unique
Write-host "Save Time on Report" $csvfile.Length
foreach($nu in $name_unique)
{
$inc = 1
foreach($au in $region_unique)
{
foreach($tu in $cost_unique)
{
foreach ($mainfile in $csvfile)
{
if (($mainfile."Region Location" -eq $au) -and ($mainfile.'Product Cost' -eq $tu) -and ($mainfile.Name -eq $nu))
{
$inc++ #Matching Counter
}
}
}
}
$inc #expected to display Row values with the total count.And export the result as csv
}
You can do this quite simply using the Group option on a Powershell object.
$csvfile = Import-csv "D:\file\filename.csv"
$csvfile | Group Name,"Region Location","Product Cost" | Select Name, Count
This gives output something like the below
Name Count
---- ------
f1, syd, 10 2
f2, syd, 10 1
f3, syd, 20 1
f4, melb, 10 2
f2, syd, 40 1
P.S. the code you provided above is not matching all of the fields, it is simply checking the Name parameter (looping through the other parameters needlessly).