The months difference between dates using PowerShell - 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
}

Related

How to split a date range into monthly subranges in PowerShell?

In Powershell, assuming:
$rangeFrom = "2020-03-01"
$rangeTo = "2020-05-13"
How could I obtain:
$monthRange[0] = "2020-03-01","2020-03-31"
$monthRange[1] = "2020-04-01","2020-04-30"
$monthRange[2] = "2020-05-01","2020-05-13"
Dates will be used in a loop as strings (from/to) on commands that do not support more than one month in range, such as:
myCommand -From $rangeFrom -To $rangeTo # keep this in one month range
By using DateTime objects you can solve the most trouble, like calculating the last day of a month or iterating over dates. You can use the following code to solve your problem:
$rangeFrom = "2019-12-15"
$rangeTo = "2020-05-13"
$monthRange = #()
$dateFrom = Get-Date $rangeFrom
$dateTo = Get-Date $rangeTo
$dateCur = Get-Date $dateFrom -Day 1
while ($dateCur -lt $dateTo) {
if (($dateCur.Year -eq $dateFrom.Year) -and ($dateCur.Month -eq $dateFrom.Month)) {
$dateBegin = $dateFrom # First month exception
} else {
$dateBegin = $dateCur
}
if (($dateCur.Year -eq $dateTo.Year) -and ($dateCur.Month -eq $dateTo.Month)) {
$dateEnd = $dateTo # Last month exception
} else {
$dateEnd = $dateCur.AddMonths(1).AddDays(-1)
}
$monthRange += [Tuple]::Create($dateBegin.toString('yyyy-MM-dd'), $dateEnd.toString('yyyy-MM-dd'))
$dateCur = $dateCur.AddMonths(1)
}
$monthRange
Output:
Item1 Item2 Length
----- ----- ------
2019-12-15 2019-12-31 2
2020-01-01 2020-01-31 2
2020-02-01 2020-02-29 2
2020-03-01 2020-03-31 2
2020-04-01 2020-04-30 2
2020-05-01 2020-05-13 2
You can access individual elements like this:
$monthRange[2].Item2
Output:
2020-02-29

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

How can I ingest random-formatted dates in PowerShell

I have a login tracker log file that is an amalgamation of multiple sources. The sources (and there are many) use a variety of date formats. I am using the resultant $objList object to hand these over to a SQL Database. When I try to then use SQL Queries, I am missing data.
Here is a small slice of the raw input, from September 2007:
Logon;Username;Server01;10/09/2007 09:56:40
Logon;Username;Server02;10/09/2007 11:26:20
Logon;Username;Server03;9/11/2007 10:16:27 AM
Logon;Username;Server04;11/09/2007 12:28:45
Notice the 3rd one is American format, the others are European. I need a way of getting these things to ingest into a script in a consistent date format. There are literally hundreds of thousands of lines in this file, so it is not realistic to go through by hand and modify anything.
Here is what I have so far.
IF ($SplitUsr.Count -eq '4')
{
$varAction = $SplitUsr[0]
IF ($varAction -eq 'Logon')
{
$varActionx = $SplitUsr[0].Trim()
$varUser = $SplitUsr[1].Trim()
$varHostname = $SplitUsr[2].Trim()
$varTime = $SplitUsr[3].Trim()
try {$datetime = [dateTime]::Parse("$varTime",([Globalization.CultureInfo]::CreateSpecificCulture('en-GB')))}
catch [System.Management.Automation.MethodInvocationException]
{
$datetime = [dateTime]::Parse("$varTime",([Globalization.CultureInfo]::CreateSpecificCulture('en-US')))
}
$objLogon = New-Object PSObject
$objLogon | Add-Member -Membertype NoteProperty -Name "Entry" -Value $intCount
$objLogon | Add-Member -Membertype NoteProperty -Name "Logon" -Value '1'
$objLogon | Add-Member -Membertype NoteProperty -Name "User" -Value $varUser
$objLogon | Add-Member -Membertype NoteProperty -Name "Hostname" -Value $varHostname
$objLogon | Add-Member -Membertype NoteProperty -Name "Date" -Value $datetime
$objList += $objLogon
Unfortunately, this is parsing them into
10 September 2007 09:56:40
10 September 2007 11:26:20
09 November 2007 10:16:27
11 September 2007 12:28:45
You can see that the 3rd example, the one with the American formatting in the raw data, came out as November instead of the 11 September (inverting the 9 and 11).
The same thing is happening all over the place. When I look at the SQL entries for December, here's what I'm getting:
07 December 2007 09:53:33
07 December 2007 11:37:48
12 July 2007 13:25:02
07 December 2007 13:26:38
07 December 2007 15:04:56
You can see that the third one somehow got the 12 and 7 inverted. This is the problem I'm trying to resolve.
Any suggestions?
Edit: A few more samples:
Logon;Username;Server01;18/11/2008 11:19:08
Logon;Username;Server02;18/11/2008 11:21:46 AM
Logon;Username;Server03;18/11/2008 14:28:30
Logon;Username;Server04;19/11/2008 09:55:50
Logon;Username;Servername;19/11/2008 14:14:09
Logon;Username;Servername;19/11/2008 14:19:56
Logon;Username;Servername;20/11/2008 12:19:57 PM
Not all the AM/PM indicate American formatting, unfortunately.
This is the "KI" you was speaking of:
$dates = #( '10/09/2007 09:56:40',
'09/10/2007 11:26:20',
'10/09/2007 10:16:27 AM',
'10/09/2007 12:28:45' )
$cultureUS = [CultureInfo]::CreateSpecificCulture("en-US")
$cultureEU = [CultureInfo]::CreateSpecificCulture("en-GB")
$maxDays = 2 # Max. allowed difference between current date and former date in days
for( $i = 0; $i -lt $dates.Count; $i++ ) {
$currentDate = [DateTime]::Parse( $dates[ $i ],$cultureEU )
if( $i -gt 0 ) {
$diffPast = New-TimeSpan -Start $lastDate -End $currentDate
}
else {
$diffPast = New-TimeSpan -Start $currentDate -End $currentDate
}
if( $diffPast.Days -gt $maxDays ) {
# check if month of current date is day of last date => culture issue
if( $currentDate.Day -eq $lastDate.Month -or $currentDate.Month -eq $lastDate.Day ) {
$currentDate = [DateTime]::Parse( $dates[ $i ],$cultureUS )
}
}
$currentDate
$lastDate = $currentDate
}
Unfortunately, not all the AM/PM indicate American date formats.
Without additional information, you cannot solve your problem, because of inherent ambiguities:
9/11/2007 10:16:27 AM
It is impossible to tell whether this is an en-US (US) timestamp that refers to the 11th day of September (month first), or a en-GB (UK) timestamp that refers to 9th day of November (day first).
Only if either the first or the second component happens to be 13 or higher is en-US or en-GB implied, and only such timestamps would be handled correctly by the try / catch logic in your question.
If you provide an additional constraint that all dates must meet, a solution is possible.
For instance, if you know that all dates fall into a given month:
# The month that all log entries are expected to fall into:
$refMonth = 9 # September, for example.
# Create an array of the cultures to use for parsing:
[cultureinfo[]] $cultures = 'en-GB', 'en-US'
'11/9/2007 17:02:15',
'9/11/2007 05:02:44 PM',
'11/9/2007 05:03:01 PM' | ForEach-Object {
$ok = $false; [datetime] $dt = 0
foreach ($culture in $cultures) {
$ok = [datetime]::TryParse($_, $culture, 'None', [ref] $dt) -and $dt.Month -eq $refMonth
if ($ok) { break }
}
if (-not $ok) { Write-Error "Not recognized as a date in the expected month: $_" }
else { $date } # Output the resulting date.
}
The above yields the following, showing that all dates were parsed as month 9 (September) dates:
Tuesday, September 11, 2007 5:02:15 PM
Tuesday, September 11, 2007 5:02:44 PM
Tuesday, September 11, 2007 5:03:01 PM

Comparing TICKS time in Powershell

I'm trying to compare two TICKS times, and i need the comparisons to be a little less precise and consider DateTime objects equal even if they were a few milliseconds apart (half minute tops) by removing any excess milliseconds and ticks from their DateTime objects (following jacubs guide).
problem is that my first ticks value ($date1) is generate from a performance counter, and i cannot convert him back to Date time again (Get-Date -Date ($date1)), getting the following error message:
Get-Date : Cannot bind parameter 'Date'. Cannot convert value
"636763462457113590" to type "System.DateTime". Error: "String was
not recognized as a valid dateTime"
This is my script:
$date1 = (Get-Counter -Counter '\TimeCheck\TIME').CounterSamples[0] | Select-Object -ExpandProperty RawValue
Get-Date $date1
Get-Date -Date ($date1) -Millisecond 0 | Select -ExpandProperty Ticks
$date2 = Get-Date
$date2.Ticks
Get-Date -Date ($date2) -Millisecond 0 | Select -ExpandProperty Ticks
$date1 -eq $date2
The only method this command works for me is if i wrote the TICKS time itself:
PS C:> Get-Date -Date 636763462457113590
Sunday, October 28, 2018 5:57:25 PM
What i'm doing wrong? even using out-string isn't working.
looks like $date1 in "Get-Date -Date ($date1)" is not numeric (based on error)
try Get-Date -Date ([int64]$date1) or [datetime][int64]$date1
this will convert from a tick count to a datetime object ...
$Ticks = 636763462457113590
$TimeFromTicks = [datetime]$Ticks
$TimeFromTicks
output = 2018 October 28, Sunday 5:57:25 PM

Powershell - Disable expired users

I need to run a command that disables users who have an account expiration date 7 days BEFORE the current date.
At the moment I have only been able to find code that looks at users who are expiring in the future
Search-ADAccount -AccountExpiring -TimeSpan "7"
or have simply already expired (can't specify date)
You can try this:
$date = (Get-Date) - (New-TimeSpan -Days 7)
Search-ADAccount -AccountExpiring | ? { $_.AccountExpirationDate -le $date }
It'll return any accounts where the expiration date is less than the date seven days ago.