Where statement in foreach loop, comparing dates - powershell

Trying to import a list of users with Name, Email, Termination Date, Stop Date and first check if either stop date or termination date has passed.
Tried adding [datetime] and using get-date $user.'stop date' but without any luck.
It seems to work with the below code without the same issues though, or I get the same error, but it does check and write out that one of the values is greater:
$StopFolder = Get-ChildItem C:\test\123\*.csv |sort LastWriteTime -descending|select -first 1
$Stoplist = Import-Csv $StopFolder -delimiter ';'
$CurrentDate = Get-Date
foreach($Date in $Stoplist){
if($CurrentDate -eq (get-date $Date.'Stop Date')){write-host Equal}
if($CurrentDate -gt (get-date $Date.'Stop Date')){write-host Greater}
if($CurrentDate -lt (get-date $Date.'Stop Date')){write-host Less}}
But the same didn't seem to work for the below and can't really figure out why. I think I need to convert it to a date, but not sure why it's working in the above and not the below, nor how exactly to convert it if get-date doesn't work.
$StopFolder = Get-ChildItem C:\test\123\*.csv |sort LastWriteTime -descending|select -first 1
$Stoplist = Import-Csv $StopFolder -delimiter ';'
$CurrentDate = Get-Date
foreach($User in $Stoplist|where($_.'stop date' -lt $CurrentDate)){
try{
$Usermail = $User.'e-mail address'
$Username = get-aduser -Filter "EmailAddress -eq '$Usermail'" -properties Enabled
if($Username.enabled){
echo $Username 'still exists and is NOT disabled' >> C:\NotDisabled.txt
}
if($Username.enabled -eq $false){
echo $Username 'still exists and is disabled' >> C:\NotDeleted.txt
}
}
catch{continue}
}
Expected result is to have it only initiate the loop if the current users stop date is less than the current date. Currently nothing happens, removing the where part and the rest seems to run fine.
Any help is much appreciated.
Edit:
CSV dates are like this:
stop date
01-02-2023
21-09-2019
21-01-2019
01-01-2019
01-01-2019

Edit: the error was not just within the logic but a typo: | where needs curly braces > | where {} not parenthesis.
Create a date from 'stop date':
(get-date -date $_.'stop date')
in one line:
foreach($User in $Stoplist|where{(get-date -date $_.'stop date') -lt $CurrentDate}){...}
Here $Stoplist|where{(get-date -date $_.'stop date') -lt $CurrentDate} is one unit and could be encapsulated in brackets:
foreach($User in ($Stoplist|where{(get-date -date $_.'stop date') -lt $CurrentDate}) ){...}
But without brackets around $User in $Stoplist the pipe | only refers to the last object $Stoplist

Related

Get a logfile for a specific date

I want to save in my computer "C:\logFiles" a specific date for logfile generated by program in another PC,
path that i will get from it the log file is "C:\Sut\Stat\03-2021.log"
Example : this file "C:\Sut\Stat\03-2021.Sutwin.log" contenant all the log of Mars month but i just want to get the log of last 7 Days from 19-03-2021 to 26-03-2021
I found this script in the internet but i doesn't work for me i need some help:
Example of the file .log in the photo attached:
Rest of image for the first screenshot :
my PC name : c01234
name of PC contenant log file : c06789
file that i will get from it the infos : 03-2021.Sutwin.log (exist in pc c06789)
i want to transfer the contents of just last 7 days in a folder in my PC c01234 with name Week11_LogFile
$log = "2015-05-09T06:39:34 Some information here
2015-05-09T06:40:34 Some information here
" -split "`n" | Where {$_.trim()}
#using max and min value for the example so all correct dates will comply
$upperLimit = [datetime]::MaxValue #replace with your own date
$lowerLimit = [datetime]::MinValue #replace with your own date
$log | foreach {
$dateAsText = ($_ -split '\s',2)[0]
try
{
$date = [datetime]::Parse($dateAsText)
if (($lowerLimit -lt $date) -and ($date -lt $upperLimit))
{
$_ #output the current item because it belongs to the requested time frame
}
}
catch [InvalidOperationException]
{
#date is malformed (maybe the line is empty or there is a typo), skip it
}
}
Based on your images, your log files look like simple tab-delimited files.
Assuming that's the case, this should work:
# Import the data as a tab-delimited file and add a DateTime column with a parsed value
$LogData = Import-Csv $Log -Delimiter "`t" |
Select-Object -Property *, #{n='DateTime';e={[datetime]::ParseExact($_.Date + $_.Time, 'dd. MMM yyHH:mm:ss', $null)}}
# Filter the data, drop the DateTime column, and write the output to a new tab-delimited file
$LogData | Where-Object { ($lowerLimit -lt $_.DateTime) -and ($_.DateTime -lt $upperLimit) } |
Select-Object -ExcludeProperty DateTime |
Export-Csv $OutputFile -Delimiter "`t"
The primary drawback here is that on Windows Powershell (v5.1 and below) you can only export the data quoted. On Powershell 7 and higher you can use -UseQuotes Never to prevent the fields from being double quote identified if that's important.
The only other drawback is that if these log files are huge then it will take a long time to import and process them. You may be able to improve performance by making the above a one-liner like so:
Import-Csv $Log -Delimiter "`t" |
Select-Object -Property *, #{n='DateTime';e={[datetime]::ParseExact($_.Date + $_.Time, 'dd. MMM yyHH:mm:ss', $null)}} |
Where-Object { ($lowerLimit -lt $_.DateTime) -and ($_.DateTime -lt $upperLimit) } |
Select-Object -ExcludeProperty DateTime |
Export-Csv $OutputFile -Delimiter "`t"
But if the log files are extremely large then you may run into unavoidable performance problems.
It's a shame your example of a line in the log file does not reveal the exact date format.
2015-05-09 could be yyyy-MM-dd or yyyy-dd-MM, so I'm guessing it's yyyy-MM-dd in below code..
# this is the UNC path where the log file is to be found
# you need permissions of course to read that file from the remote computer
$remotePath = '\\c06789\C$\Sut\Stat\03-2021.log' # or use the computers IP address instead of its name
$localPath = 'C:\logFiles\Week11_LogFile.log' # the output file
# set the start date for the week you are interested in
$startDate = Get-Date -Year 2021 -Month 3 -Day 19
# build an array of formatted dates for an entire week
$dates = for ($i = 0; $i -lt 7; $i++) { '{0:yyyy-MM-dd}' -f $startDate.AddDays($i) }
# create a regex string from that using an anchor '^' and the dates joined with regex OR '|'
$regex = '^({0})' -f ($dates -join '|')
# read the log file and select all lines starting with any of the dates in the regex
((Get-Content -Path $remotePath) | Select-String -Pattern $regex).Line | Set-Content -Path $localPath

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($_)
}

Comparing dates of a CSV column in PowerShell

I have a CSV file spreadsheet (converted from an Excel xlsx) with around 21 columns and 74,000 rows. The four columns of interest to me are columns having to do with an employees start date, a termination date, a department name, and a vice president they report to.
I am trying to write a script that will return all employees whom have reached their start date, have not been terminated, work in a department that contains 'HR' in the name, and report to a specific VP. I will elaborate on my specific issues after the block of code.
$Lawson = Import-Csv .\Documents\Lawson_HR.csv
$startDate = $Lawson | where [datetime]::ParseExact($_.'LAW HIRE DATE', 'dd-MM-yyyy', $null) -le (Get-Date)
$endDate = $startDate | where {$_.'LAW TERM DATE' -eq ''}
$HR = $endDate | where {$_.'LAW DEPT NAME' -match 'HR'}
$VP = $endDate | where {$_.'VICE PRESIDENT' -match 'Croner'}
First, the $startDate variable does not work, I am unsure of the syntax needed to compare a given date (from the CSV) to today's date. (The $endDate variable functions as it should, but I was told that the method used is unreliable.)
Also, I would like to search the Dept Name column in each row for any instance of the letters 'HR' (note: dept names could be things like 'HR - Career Services' or 'HR - Diversity'. I want all rows that have 'HR' anywhere in the Dept Name field). I get the feeling the -match operator is not the way to do that, but I'm not certain.
Similarly, I would like for the $VP variable to return all items in which the Vice President column has a given name (in this case, Croner).
This line needs curly braces { } but looks otherwise OK to me:
$startDate = $Lawson | where { [datetime]::ParseExact($_.'LAW HIRE DATE', 'dd-MM-yyyy', $null) -le (Get-Date) }
To do a simple partial match you're better off using -Like and a wildcard character as -Match uses regex (although should work).
Also I just noticed you were piping the $enddate variable not $lawson:
$HR = $Lawson | where {$_.'LAW DEPT NAME' -like '*HR*'}
If you're trying to do all of these criteria together, just combine them with -and:
$Lawson | where { [datetime]::ParseExact($_.'LAW HIRE DATE', 'dd-MM-yyyy', $null) -le (Get-Date) -and $_.'LAW TERM DATE' -eq '' -and $_.'LAW DEPT NAME' -like '*HR*' -and $_.'VICE PRESIDENT' -match 'Croner'}

powershell Get-Winevent SWITCH MATCH issue

'm running this powershell command and saving the output in a csv.
powershell "Get-WinEvent -EA SilentlyContinue -FilterHashtable #{Logname='System';ID=42}| SELECT-Object #{Label = 'TimeCreated'; Expression = {Get-Date $_.TimeCreated -Format 'yyyy-MM-dd HH:mm:ss'}},#{Label = 'DayOfWeek'; Expression = {(Get-Date $_.TimeCreated).DayOfWeek}},ID,#{l='ID Description';e={Switch ($_) { {$_.ID -eq '42'}{'Type=Sleep matched using EventID';break} {$_.MESSAGE -Match 'Sleep Reason: Application API'}{Type='Sleep matched using Message';break} }}},MESSAGE|ConvertTo-Csv -NoTypeInformation | %{ $_ -replace """`r`n""",',' } | select -Skip 1 | Out-File -Append c:\logs\timeline\TEMP_TimeLine.csv"
I get the expected results as below:
"2014-05-10 00:00:04","Saturday","42","Type=Sleep matched using EventID","The system is entering sleep.,,Sleep Reason: Application API"
"2014-05-09 00:00:02","Friday","42","Type=Sleep matched using EventID","The system is entering sleep.,,Sleep Reason: Application API"
"2014-05-08 00:00:02","Thursday","42","Type=Sleep matched using EventID","The system is entering sleep.,,Sleep Reason: Application API"
But, if i switch the positions of the two case statements inside the switch, i'm not getting the expected output(The derived field 'ID Description' is blank). I am trying to get mix of string matches on the message field and EventID field to be working together.
This is what i'm trying:
powershell "Get-WinEvent -EA SilentlyContinue -FilterHashtable #{Logname='System';ID=42}| SELECT-Object #{Label = 'TimeCreated'; Expression = {Get-Date $_.TimeCreated -Format 'yyyy-MM-dd HH:mm:ss'}},#{Label = 'DayOfWeek'; Expression = {(Get-Date $_.TimeCreated).DayOfWeek}},ID,#{l='ID Description';e={Switch ($_) { {$_.MESSAGE -Match 'Sleep Reason: Application API'}{Type='Sleep matched using Message';break} {$_.ID -eq '42'}{'Type=Sleep matched using EventID';break} }}},MESSAGE|ConvertTo-Csv -NoTypeInformation | %{ $_ -replace """`r`n""",',' } | select -Skip 1 | Out-File -Append c:\logs\timeline\TEMP_TimeLine.csv"
The Message field clearly has the string 'Sleep Reason: Application API' as we can see from the first output. Wondering whats going on here... Any clues powershell experts?
Ok, two issues I see:
A) You're probably breaking your own script. I'll get to that in a sec.
B) You're missing a ' on the $_.Message line. Type='Sleep should be 'Type=Sleep
Ok, back to point A. I'll start with ;break. In 99% of cases don't do it, you'll make the scripting gods angry, and you wouldn't like them when they're angry. In most cases you want to use ;Continue instead. Break literally breaks out of things, and depending on where it's used it can break out of parent loops entirely stopping part way through a set of things. Continue on the other hand moves to the end of the current loop, skipping anything else. Same thing? Kinda, sorta, but Continue won't break a ForEach-Object loop like Break will.
So, with that said, let's try this in your switch:
Switch ($_) {
{$_.ID -eq '42'}{'Type=Sleep matched using EventID';continue}
{$_.MESSAGE -Match 'Sleep Reason: Application API'}{'Type=Sleep matched using Message';continue}
}
Ok, that's great, along with the whole ' issue in point B that would probably fix the code in general.
So, with that said, why are you running it like that? Dear lord, running an insanely long one liner is just crazy. Save it to a .PS1 file, and if you're calling it from a batch file then call the script file, but ug, that's just hard to work with in general, it's no wonder you missed the ' in the middle of that line. If you are calling it from a batch file, name it GetSleepLogs.ps1 (or whatever you want, just modify the file name in the command) and try this:
PowerShell.exe -WindowStyle Hidden -ExecutionPolicy Bypass -File GetSleepLogs.ps1
Edit: I didn't like the convertto-CSV | select -skip 1 | %{ %_ -replace ...} | Out-File thing, it just seemed clunky to me. Also, all the impromptu hashtables on the Select command were a little hard to follow. Check out this alternative that creates 1 object with several properties, and then just pipes that to Export-CSV with the -append and -NoTypeInformation switches set which should just tack it to the bottom of an existing CSV file.
Get-WinEvent -EA SilentlyContinue -FilterHashtable #{Logname='System';ID=42}| ForEach{[PSCustomObject][Ordered]#{
'TimeCreated' = Get-Date $_.TimeCreated -Format 'yyyy-MM-dd HH:mm:ss'
'DayOfWeek' = (Get-Date $_.TimeCreated).DayOfWeek
'ID' = $_.ID
'ID Description' = Switch($_){
{$_.ID -eq '42' -AND $_.Message -match 'Sleep Reason: Application API'}{'Type=Sleep matched using EventID and Message';continue}
{$_.ID -eq '42'}{'Type=Sleep matched using EventID';continue}
{$_.Message -match 'Sleep Reason: Application API'}{'Type=Sleep matched using Message';continue}}
'MESSAGE' = $_.Message.replace("`r`n`r`n","`r`n") -replace "((?<!`")`r`n|`n|`r)", ","
}}|Export-Csv C:\temp\TimeLine.csv -NoTypeInformation -append

LastAccessTime and get-date Comparison

I am parsing through a directory with multiple sub-directories and want to compare the LastAccessed time with the get-date time to see if the file has been accessed since yesterday, and based on that I will either delete the file or leave it alone. I have tried piping the get-date results out to a text file and pull it back as a string, I have tried wildcard I have even gone as far as using the -like as opposed to -eq in order to get the comparison to work, but it is not properly comparing the data. Any help would be greatly appreciated.
Here is my current code:
$servers="servera","serverb"
$date3=get-date -Format d
foreach($a in $servers){
$CTXGPDir="\C$\ProgramData\Citrix\GroupPolicy"
$CTXGPDirFP="\\"+"$a"+"$CTXGPDir"
$CTXGPUserDirstoRM=Get-ChildItem "$CTXGPDirFP"|where-Object{$_.Name -notlike "*.gpf"}
foreach($i in $CTXGPUserDirstoRM){
$datestring="$date3"+" *"
$CTXUserGPPath="\C$\ProgramData\Citrix\GroupPolicy\$i"
$CTXUserGPFP="\\"+"$a"+"$CTXUserGPPath"
$file=get-item $CTXUserGPFP
$isFileInactive=$file|select-object -expandproperty LastAccessTime
write-host $file
write-host $isFileInactive
write-host $datestring
if($isFileInactive -like "$datestring *"){write-host "$CTXUserGPFP on $a has lastwritetime of $isFileInactive and should NOT BE deleted"}
if($isFileInactive -notlike "$datestring *"){write-host "$CTXUserGPFP on $a has lastwritetime of $isFileInactive and SHOULD BE deleted"}
}
Your date comparison is deeply flawed.
get-date -format d returns a String representing the current date based on your regional settings.
get-childitem <file> | select -expandproperty lastaccesstime returns a DateTime object, which gets formatted as a "long" date/time using your regional settings.
To compare these two dates effectively, you need to convert the latter to the same format.
$isFileInactive=($file|select-object -expandproperty LastAccessTime).ToShortDateString()
$isFileInactive is now a String formatted the same as you get with get-date -format d and you can make a proper comparison.
if($isFileInactive -eq $datestring){write-host "$CTXUserGPFP on $a has lastwritetime of $isFileInactive and should NOT BE deleted"}
If you have to deal with timezones, you may want to amend it to add .ToLocalTime() before ToShortDateString();