Working with unusual date formats in Powershell? - powershell

I am working on a GUI-based Powershell tool to help users easily find out when their AD password is going to expire. Due to Covid restrictions, most users and not on-site and rely on VPN to connect to AD. A by-product of this is that many do not see the automatic pop-up in Windows to remind them of them to set a new password soon. Those that are on-site see the notification OK. It's a not a group-policy problem. The tool will be will be rolled-out in two different languages - one representing the 'mothership' (where is English is not normally spoken) and English for all other countries.
Some of the (mostly) Eastern European countries use a short date format that reads like 'd.M.yyyy.' - according to settings menu when one changes the 'Region' setting in Windows.
The tool calculates the difference between today and the expiry data and outputs the number of days to a text field. The actual expiry is display correctly in its own text field.
Some of the source code behind it all...
$thisUser = [Environment]::UserName
#make string
"net user " + $thisUser + " /domain"
#make cmd string
$fetch_net_user = "net user " + $thisUser + " /domain"
#execute command and store to variable
$net_user_data_array = Invoke-Expression $fetch_net_user
#Gets password expiry date (with header)
$password_expiry_with_header = $net_user_data_array[11]
#extracts only the date and assigns to new variable (the hours and minutes) can be ignored)
$password_expiry_as_string = $password_expiry_with_header.Split(' ')[-2]
# Home countries - works OK
try{
$password_expiry_as_dateTime = [datetime]::ParseExact($password_expiry_as_string, 'dd.MM.yyyy', $null)
}
# Others - works OK
catch{
$password_expiry_as_dateTime = [datetime]::ParseExact($password_expiry_as_string, 'dd/MM/yyyy', $null)
# where problem occurs
catch{
$password_expiry_as_dateTime = [datetime]::ParseExact($password_expiry_as_string, 'd.M.yyyy.', $null)
}
finally{
#show GUI error window...
}
#fetch date... converted to yesterday to fix an off-by-one bug!
$today = Get-Date
$rightNow = $today.AddDays(-1)
#calc days left
$daysRemaining = (New-TimeSpan -Start $rightNow -End $password_expiry_as_dateTime).Day
# some other code follows, manipulating date values etc. Works with most date formats.
When executed the script will throw several errors.
The first is...
Exception calling "ParseExact" with "3" argument(s): "String was not recognized as a valid DateTime."
Others will follow such as
You cannot call a method on a null-valued expression.
As the difference between today and the expiry date cannot be calulated.
Is there any easy fix for this? I'd rather avoid having to write a long list of 'if' statments for each country/culture. Thanks!
I'm sure the code could be a little bit more elegant, and that will be addressed in a later version. Right now, getting it to work some of the more obscure date formats is my priority.
Something else that I should stress is that this tool works in a 'read only' capacity. No 'Set-Item' commands are used.
Regards,
WL

You have 2 problems:
Multiple date formats used interchangeably
Trailing dots on some strings
The first problem can be solved by passing multiple format strings to ParseExact() - it'll try each in order until it successfully parses the input string (or reaches the end of the list):
[datetime]::ParseExact($password_expiry_as_string, [string[]]#('dd.MM.yyyy', 'dd/MM/yyyy', 'dd.M.yyyy', 'd.M.yyyy'), $null, [System.Globalization.DateTimeStyles]::None)
The second problem can be solved by trimming trailing dots:
[datetime]::ParseExact($password_expiry_as_string.TrimEnd('.'), [string[]]#('dd.MM.yyyy', 'dd/MM/yyyy', 'dd.M.yyyy', 'd.M.yyyy'), $null, [System.Globalization.DateTimeStyles]::None)

Related

Set Expiry Date with CSV containing DD-MMM-YYYY format

Our HR Department's Termination system sends us a report at intermittent intervals of people who are leaving/people who have left.
Import-Csv -Path \C\Users\Leaver.csv | ForEach-Object {
$EmployeeNumber = $_.EmployeeNumber
$TerminationDate = $_.TerminationDate
$objADUser = (Get-ADUser -Filter {employeeid -eq $EmployeeNumber}).samAccountName
}
The report that HR give us doesn't have their sAMAccountName or even their email. Fortunately, it does have their Employee ID. I'm using this in the script to get their SAM name since I figure that's easier to work with.
Problem is, the termination date they give is actually the last day they work, not the first day of non-work.
Normally I'd have
Set-ADAccountExpiration -Identity $objADUser -DateTime "$TerminationDate"
But that means they can't work on their last day. I also can't add a time like "23:59:59" as Powershell doesn't seem to like that with a variable.
There's also the fact that the date format is in DD-MMM-YYYY (EG 01-Jan-2000) which does make it difficult to work with.
Anyone know what the best syntax to use here is?

Run a command in middle of Write-Host

I have a write-host line that I want to execute a command in the middle of, so the output will be inserted in the middle of the write-host string.
Basically I have a txt file that holds configuration data for a suite of scripts, one of the configurations is the format for dates and time. For example, there is a configuration for the year format which is 'YYYY' and it is written to $Year.
So what I would like to do is something like this:
Write-Host "The year is " Get-Date -Format $Year.ToLower()
What I expect to see on my screen when this is ran, is
The year is 2019
Now I know I can declare another variable with this logic and just have...
Write-Host "The year is $NewVariable"
...but I was hoping not to create another variable. This is a dumb-ed down example of my scrip, so I would be creating a lot of variables if I go this rout. Please note I am using .ToLower() to compensate for the user's input into the configuration text file.
In order to print the year of the Get-Date run this:
Write-Host "The year is $(Get-Date -Format yyyy)"
This way you will always get the year that is generated by Get-Date
This works fine....
$year = "2019"
Write-Host "The year is" (Get-Date -Format $Year.ToLower())

Working with dates in extensionAttributes and Get-Date

I'm attempting to use Active Directory extensionAttributes to keep track of certain dates (like start date, termination date, etc) so that I can trigger certain actions when that date occurs.
I'm having an issue with the different variations that a date can be entered in (M/D/YY, MM/DD/YY, MM/DD/YYYY, etc). For example, I can use Get-Date to output to a format of M/D/YYYY, but I run into issues when someone enters MM/DD/YY.
Is there a way to make this work so that it can accept other variations (as long as it's month/date/year)?
Here are a couple of lines from the script in question. This runs once a day, and checks for new users starting the following day.
$StartingOn = (Get-Date).AddDays(1).ToShortDateString()
$NewUserCheck = Get-QADUser -DontUseDefaultIncludedProperties -IncludedProperties extensionAttribute11 | where { $_.extensionAttribute11 -eq $StartingOn }
Notice how it only returns as long as the date equals the Get-Date output. It was the only way I was able to get this to work properly. Even at that, if someone typed in 07/20/15, the output would return nothing.
Don't try to compare date strings. Use DateTime comparison which won't care about formatting details e.g.:
$StartingOn = (Get-Date).AddDays(1)
$NewUserCheck = Get-QADUser -DontUseDefaultIncludedProperties -IncludedProperties extensionAttribute11 |
Where { [DateTime]($_.extensionAttribute11) -eq $StartingOn}

DeleteContent not deleting items correctly/at all

To preface this all, I am running this script in the US against a US mailbox server. The mail that I am wanting to delete is also mail that I have exported to a PST.
The mailbox Export uses a ContentFilter while a deleteContent uses a SearchQuery. These 2 acts of grabing information seem to act very different to me.
I have successfully been able to export the mail I want to a PST, however deleting the content has been posing a huge problem.
I am trying to delete mailbox items that are before the current get-date using the code below. I swear I have been following Microsoft's documentation on this, however nothing is working for me.
$date = (get-date -hour 00 minute 00 second 00).ToShortDateString()
Search-Mailbox -Identity "id" -SearchQuery "Received:<$($date)" -deleteContent -force
This is not working. I am getting an error:
The property keyword isn't supported.
+ CategoryInfo : InvalidArgument: (:) [], ParserException
Please see edits below as I have fixed my string, however the issue still persists. It seems as though the < is what is messing up the query.
EDIT
"Received:<'$date'" - This executes when I specify it as my SearchQuery however no results come back.
It seems like the < is what is messing up my query. Once I remove the < and leave the query as "Received:'$date'" it deletes all emails from todays date.
Also, it appears that the time is offset by 5 hours when doing this. I am in the US and the mailbox server I am running this on is in the US, yet it is still acting like it could be using UTC time. How do I fix this? More importantely though, why isn't my less than working.
EDIT 2:
I have also attempted to do querys such as "Received -lt '$date'" this is not working also.
Try: $date.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
While the above will get you an ISO 8601 datetime, it doesn't look like the search query is using the full thing. I did find that the below syntax seem to work at least for the date part:
$date = (get-date).Date.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
Search-Mailbox -Identity "id" -SearchQuery "Received<$date" -deleteContent -force
The SearchQuery argument is using KQL, which is documented at: http://msdn.microsoft.com/en-us/library/office/ee558911(v=office.15).aspx
Unfortunately since it seems Exchange is ignoring the time part, I'm guessing that this is going to be limited to filtering at UTC midnight. Looking at the docs, there is shorthand that will give you the same results:
Search-Mailbox -Identity "id" -SearchQuery "Received<today" -deleteContent -force

Powershell (exchange message search)

I've literally just C+P this code into my PowerShell, related to this question I asked on Super User.
It gives me exactly what I'm looking for but only for today's emails, can someone tell me how to add a a filter for emails the last two months for example? I looked through the code and I can't see anything related to data range so there might be another cmdlet that needs to be ran?
From a cursory glance at the script you linked, the individual emails appear to be iterated through in a foreach loop that uses the output of Get-MessageTrace as the collection of objects to iterate through. According to this documentation, Get-MessageTrace has a -StartDate and -EndDate parameter that you can specify a range of dates with.
So you just need to use those parameters to get a longer range of dates. Here's an untested example of what you'll probably need to do for the past two months:
Get-MessageTrace -StartDate (Get-Date).AddMonths(-2) -EndDate (Get-Date)
Edit: According to the parameter documentation, you may have to do some additional formatting after getting the date. I'm unfortunately not somewhere where I can test this, but here's what it says:
Use the short date format defined in the Regional Options settings for the computer on which the command is run. For example, if the computer is configured to use the short date format mm/dd/yyyy, enter 03/01/2010 to specify March 1, 2010. You can enter the date only, or you can enter the date and time of day. If you enter the date and time of day, you must enclose the argument in quotation marks ("), for example, "10/05/2010 5:00 PM".
This may not be necessary since you're already passing a DateTime object (the output of Get-Date) instead of a string... but worth mentioning, if you simply want to hard-code a string instead of getting the current time.