PowerShell Get-ADUser, output to JSON with formatted DateTime - json

I'll say first that I'm not very experienced with PowerShell, so many things are not obvious to me.
I've got the following command:
$JSON = Get-ADUser -Filter "SAMAccountName -eq 'jdray'" -Properties SAMAccountName,
Name,
DisplayName,
Manager,
SAMAccountType,
pwdLastSet,
Created,
AccountExpirationDate,
lastLogonTimestamp,
DistinguishedName|Select-object -Property SAMAccountName,
Name,
#{name='Manager';expression={ ([ADSI]"LDAP://$($_.manager)").displayname }},
Enabled,
DistinguishedName,
Created,
AccountExpirationDate,
LastLogonTimestamp,
PasswordLastSet|ConvertTo-Json
The output of this is:
{
"SAMAccountName": "jdray",
"Name": "Ray, James",
"Manager": "Lady, Boss",
"Enabled": true,
"DistinguishedName": "CN=Ray\\, James,OU=...[yadda yadda yadda]...DC=com",
"Created": "\/Date(1554387070000)\/",
"AccountExpirationDate": "\/Date(1643702400000)\/",
"LastLogonTimestamp": 132732539928955494,
"pwdLastSet": 132729945927805005
}
First of all, I'm confused why AccountExpirationDate and Created have one format, but LastLogonTimestamp and pwdLastSet are formatted differently.
What I really want is to have all the DateTime objects output in a human-readable DateTime format. I tried -ExpandProperty, but couldn't figure out how to make it work. It appears to add a property named DateTime to the end of the output, formatted reasonably: "DateTime": "Thursday, April 4, 2019 7:11:10 AM", but it doesn't name it. The Microsoft documentation on the subject is very light on useful examples, as is the rest of the web.
I tried following the #{} format for the Manager expression, which I copied from someone else's script. Unfortunately my PowerShell isn't strong enough to figure out a reasonable expression. In C#, something like Created.ToShortDateString() would suffice (presuming Created is a DateTime).
So, how do I consistently output named, human-readable DateTime property values in a JSON array?
EDIT
Using a proposed answer below, I modified the four timestamp fields using the same pattern:
#{n='Created';e={[DateTime]::FromFileTime($_.Created)}},
#{n='AccountExpirationDate';e={[DateTime]::FromFileTime($_.AccountExpirationDate)}},
#{n='LastLogonTimestamp';e={[DateTime]::FromFileTime($_.lastLogonTimestamp)}},
#{n='pwdLastSet';e={[DateTime]::FromFileTime($_.pwdLastSet)}}
Here's the (frustrating) result I got:
"Created": "\/Date(1554387070000)\/",
"AccountExpirationDate": "\/Date(1643702400000)\/",
"LastLogon": {
"value": "\/Date(1628780392895)\/",
"DateTime": "Thursday, August 12, 2021 7:59:52 AM"
},
"pwdLastSet": 132729945927805005
Also of note, here's the output for the four DateTime fields before it gets translated to JSON:
Created : 4/4/2019 7:11:10 AM
AccountExpirationDate : 2/1/2022 12:00:00 AM
LastLogonTimestamp : 8/12/2021 7:59:52 AM
PasswordLastSet : 8/9/2021 7:56:32 AM
Why does that formatting work for one DateTime instance but not the rest?
How do I extract DateTime from the format of the pwdLastSet field, which is clearly a different animal?
How do I get a simple property:value pair, such as "LastLogonTimestamp": "Thursday, August 12, 2021 7:59:52 AM"?

Use DateTime.FromFileTime(LastLogonTimestamp)
$JSON=Get-ADUser -Filter {Enabled -eq $true} -Properties Name,Manager,LastLogon |
Select-Object Name,Manager,#{n='LastLogon';e={[DateTime]::FromFileTime($_.LastLogon)}}

Related

Filtering JSON by timestamp in powershell

I have retrieved json log data from a rest API as follows
[
{
"id": "6523276",
"type": "logs",
"attributes": {
"created-at": "2022-02-22T10:50:26Z",
"action": "delete",
"resource-name": "DocumentABC.docx",
"user-name": "Joe Smith"
}
},
{
"id": "6523275",
"type": "logs",
"attributes": {
"created-at": "2022-02-22T10:03:22Z",
"action": "create",
"resource-name": "Document123.docx",
"user-name": "Joe Smith"
}
},
{
"id": "6523274",
"type": "logs",
"attributes": {
"created-at": "2022-02-22T06:42:21Z",
"action": "open",
"resource-name": "123Document.docx",
"user-name": "Joe Smith"
}
}
]
I need to Post the json to another web app but I only want the last hour of logs.
In the json example above, the current time was 2022-02-22T10:55:22Z, therefore I'm only interested in the first two log entries.
For example
[
{
"id": "6523276",
"type": "logs",
"attributes": {
"created-at": "2022-02-22T10:50:26Z",
"action": "delete",
"resource-name": "DocumentABC.docx",
"user-name": "Joe Smith"
}
},
{
"id": "6523275",
"type": "logs",
"attributes": {
"created-at": "2022-02-22T10:03:22Z",
"action": "create",
"resource-name": "Document123.docx",
"user-name": "Joe Smith"
}
}
]
Here is my powershell v7 script
$json = $json | ConvertFrom-Json
$filterTime = (Get-date).AddHours(-1)
$RFCfilterTime = [Xml.XmlConvert]::ToString($filterTime,[Xml.XmlDateTimeSerializationMode]::Utc)
$Filteredjson = $json | Where-Object $json.attributes[0] -ge $RFCfilterTimefilterDate
$jsonToPost = ConvertTo-Json -InputObject #($Filteredjson) -Depth 5
The problem is ConvertFrom-Json changes the 'created-at' from RFC3339 format to 'datetime' format.
Therefore the Where-Object filter doesn't work...
id type attributes
-- ---- ----------
6523276 logs #{created-at=22/02/2022 10:50:26 AM; action…
6523275 logs #{created-at=22/02/2022 10:03:22 AM; action…
6523274 logs #{created-at=22/02/2022 6:42:21 AM; action=…
How do I change all of the 'created-at' objects back to RCF3339 format?
Is the
$json | Where-Object $json.attributes[0] -ge $RFCfilterTimefilterDate
statement being used correctly?
Is there any easier way altogether?
Your approach should work in principle, but there was a problem with your Where-Object statement - see the bottom section.
Mathias' answer shows how to work with the [datetime] instances that result from ConvertTo-Json's parsing directly, but a bit more work is required:
Indeed, in PowerShell (Core) v6+ ConvertFrom-Json (which with JSON web services is used implicitly by Invoke-RestMethod) automatically deserializes ISO 8601-format date-time strings such as "2022-02-22T10:03:22Z" into [datetime] System.DateTime instances, and, conversely, on (re)serialization with ConvertTo-Json, [datetime] instances are (re)converted to ISO 8601 strings.
While this enables convenient chronological comparisons with other [datetime] instances, such as returned by Get-Date, there is a major pitfall: Only [datetime] instances that have the same .Kind property value compare meaningfully (possible values are Local, Utc, and Unspecified, the latter being treated like Local in comparisons).
Unfortunately, as of PowerShell 7.2.1, you don't get to control what .Kind of [datetime] instances Convert-FromJson constructs - it is implied by the specific date-time string format of each string recognized as an ISO 8601 date.
Similarly, on (re)serialization with ConvertTo-Json, the .Kind value determines the string format.
See this answer for details.
In your case, because your date-time strings have the Z suffix denoting UTC, [datetime] instances with .Kind Utc are constructed.
Therefore, you need to ensure that your comparison timestamp is a Utc [datetime] too, which calling .ToUniversalTime() on the Local instance that Get-Date outputs ensures:
# Note the need for .ToUniversalTime()
$filterTime = (Get-Date).ToUniversalTime().AddHours(-1)
# Note: Only works as intended if all date-time strings are "Z"-suffixed
$filteredData = $data | Where-Object { $_.attributes.'created-at' -ge $filterTime }
However, at least hypothetically a given JSON document may contain differing date-time string formats that result in different .Kind values.
The way to handle this case - as well as the case where the string format is consistent, but not necessarily known ahead of time - you can use the generally preferable [datetimeoffset] (System.DateTimeOffset) type, which automatically recognizes timestamps as equivalent even if their expression (local vs. UTC) is different:
# Note the use of [datetimeoffset]
[datetimeoffset] $filterTime = (Get-Date).AddHours(-1)
# With this approach the specific format of the date-time strings is irrelevant,
# as long as they're recognized as ISO 8601 strings.
$filteredData = $data |
Where-Object { [datetimeoffset] $_.attributes.'created-at' -ge $filterTime }
Note: Strictly speaking, it is sufficient for the LHS of the comparison to be of type [datetimeoffset] - a [datetime] RHS is then also handled correctly.
Potential future improvements:
GitHub issue #13598 proposes adding a -DateTimeKind parameter to ConvertFrom-Json, so as to allow explicitly requesting the kind of interest, and to alternatively construct [datetimeoffset] instances.
As for what you tried:
Is the $json | Where-Object $json.attributes[0] -ge $RFCfilterTimefilterDate
statement being used correctly?
No:
You're using simplified syntax in which the LHS of the comparison (the -Property parameter) must be the name of a single (non-nested) property directly available on each input object.
Because nested property access is required in your case, the regular script-block-based syntax ({ ... }) must be used, in which case the input object at hand must be referenced explicitly via the automatic $_ variable.
.attributes[0] suggests you were trying to access the created-at property by index, which, however, isn't supported in PowerShell; you need to:
either: spell out the property's name, if known: $_.attributes.'created-at' - note the need to quote in this case, due to use of the nonstandard - char. in the name.
or: use the intrinsic .psobject member that provides reflection information about any given object: $_.attributes.psobject.Properties.Value[0]
Thus, with spelling out the property name - and with making sure that the LHS [datetime] value is represented as an ISO 8601-formatted string too, via .ToString('o') - your statement should have been:
$json | Where-Object {
$_.attributes.'created-at'.ToString('o') -ge $RFCfilterTimefilterDate
}
The fact that newer version of ConvertFrom-Json implicitly parses timestamps as [datetime] is actually to your advantage - [datetime] values are comparable, so this simply means you can skip the step where you convert the threshold value to a string:
$data = $json | ConvertFrom-Json
$filterTime = (Get-Date).AddHours(-1)
$filteredData = $data | Where-Object {$_.attributes.'created-at' -ge $filterTime}
$jsonToPost = ConvertTo-Json -InputObject #($filteredData) -Depth 5

Parsing JSON file in powershell with specific characters

I am trying to get the values in powershell within specific characters. Basically I have a json with thousands of objects like this
"Name": "AllZones_APOPreface_GeographyMatch_FromBRE_ToSTR",
"Sequence": 0,
"Condition": "this.TripOriginLocationCode==\"BRE\"&&this.TripDestinationLocationCode==\"STR\"",
"Action": "this.FeesRate=0.19m;this.ZoneCode=\"Zone1\";halt",
"ElseAction": ""
I want everything within \" \"
IE here I would see that BRE and STR is Zone1
All I need is those 3 things outputted.
I have been searching how to do it with ConvertFrom-Json but no success, maybe I just havent found a good article on this.
Thanks
Start by representing your JSON as a string:
$myjson = #'
{
"Name": "AllZones_APOPreface_GeographyMatch_FromBRE_ToSTR",
"Sequence": 0,
"Condition": "this.TripOriginLocationCode==\"BRE\"&&this.TripDestinationLocationCode==\"STR\"",
"Action": "this.FeesRate=0.19m;this.ZoneCode=\"Zone1\";halt",
"ElseAction": ""
}
'#
Next, create a regular expression that matches everything in between \" and \", that's under 10 characters long (else it'll match unwanted results).
$regex = [regex]::new('\\"(?<content>.{1,10})\\"')
Next, perform the regular expression comparison, by calling the Matches() method on the regular expression. Pass your JSON string into the method parameters, as the text that you want to perform the comparison against.
$matchlist = $regex.Matches($myjson)
Finally, grab the content match group that was defined in the regular expression, and extract the values from it.
$matchlist.Groups.Where({ $PSItem.Name -eq 'content' }).Value
Result
BRE
STR
Zone1
Approach #2: Use Regex Look-behinds for more accurate matching
Here's a more specific regular expression that uses look-behinds to validate each field appropriately. Then we assign each match to a developer-friendly variable name.
$regex = [regex]::new('(?<=TripOriginLocationCode==\\")(?<OriginCode>\w+)|(?<=TripDestinationLocationCode==\\")(?<DestinationCode>\w+)|(?<=ZoneCode=\\")(?<ZoneCode>\w+)')
$matchlist = $regex.Matches($myjson)
### Assign each component to its own friendly variable name
$OriginCode, $DestinationCode, $ZoneCode = $matchlist[0].Value, $matchlist[1].Value, $matchlist[2].Value
### Construct a string from the individual components
'Your origin code is {0}, your destination code is {1}, and your zone code is {2}' -f $OriginCode, $DestinationCode, $ZoneCode
Result
Your origin code is BRE, your destination code is STR, and your zone code is Zone1

powershell json DateFormat

returned data from rest-API as JSON where the data/time is formated as
{
"userKey": ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤,
"userId": "¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤",
"userEmail": "¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤",
"userPrincipalName": "¤¤¤¤¤¤¤¤¤¤¤¤¤¤",
"displayName": "¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤",
"intuneLicensed": true,
"isDeleted": true,
"startDateInclusiveUTC": "2017-10-16T00:00:00Z",
"endDateExclusiveUTC": "9999-12-31T00:00:00Z",
"isCurrent": true,
"rowLastModifiedDateTimeUTC": "2017-10-17T00:24:11.8233333Z",
"PictureUrl": "¤¤¤¤¤¤¤¤¤¤¤¤.dk"
}
date/time is not formated properly, the data is from Intune DataWareHouse API
the data is requested via PowerShell my Question is it a PowerShell Shell Bug or this how JSON handel Data Formats
The Date Shold Appear as
MM/DD/YY HH:MM:SS
see the picture
The date is simply a string containing the date as ISO-8601 (because JSON doesn't have a DateTime type). Parse the date, and then format it as a different string:
PS> [datetime]::parse("9999-12-31T00:00:00Z").Tostring('MM/dd/yy HH:mm:ss')
12-31-99 01:00:00
i did a Convertfrom-json and then looping through every Note Property, if there is a better approach feel free to comment :)
$IntuneCollectionData | get-member -type NoteProperty | foreach-object {
if ($_.value -match "(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+)|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d)|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d)")
{
$NewDateTime = [datetime]::parse("$($_.value)").Tostring('MM/dd/yy HH:mm:ss')
$_.value = $NewDateTime
}
}

How to select a date range from a JSON string by using jq?

I have a JSON string like this (MacOS):
[{
"id": 3624,
"created_at": "2016-10-21T20:51:16.000+08:00",
},
{
"id": 3625,
"created_at": "2016-10-22T08:09:16.000+08:00",
},
{
"id": 3626,
"created_at": "2016-10-23T09:19:55.000+08:00",
}]
I wanna select "created_at" from "2016-10-21" to "2016-10-22";
I wanna get result like this:
[{
"id": 3624,
"created_at": "2016-10-21T20:51:16.000+08:00",
},
{
"id": 3625,
"created_at": "2016-10-22T08:09:16.000+08:00",
}]
Can someone point me in the right direction?
The problem is solved.
Now,i use this code to select date right to the minute,i hope it's useful for others:
jq --arg s '2016-10-26T18:16' --arg e '2016-10-27T20:24' '[($s, $e) | strptime("%Y-%m-%dT%H:%M") | mktime] as $r
| map(select(
(.updated_at[:19] | strptime("%Y-%m-%dT%H:%M:%S") | mktime) as $d
| $d >= $r[0] and $d <= $r[1]))' <<< "$requestJson"
For a more robust solution, it would be better to parse the dates to get its components and compare those components. The closest you can get is to use strptime/1 to parse the date which returns an array of its components. Then compare the components to check if it's in range.
The array that strptime returns are the components:
year (%Y)
month (%m)
date (%d)
hours (%H)
minutes (%M)
seconds (%S)
day of week (%w)
day of year (%j)
Since you're only comparing the dates, the comparisons should only look at the first 3 components.
$ jq --arg s '2016-10-21' --arg e '2016-10-22' '
[($s, $e) | strptime("%Y-%m-%d")[0:3]] as $r
| map(select(
(.created_at[:19] | strptime("%Y-%m-%dT%H:%M:%S")[0:3]) as $d
| $d >= $r[0] and $d <= $r[1]
))
' input.json
Since you're running on a Mac, I'd expect these methods would be available to you in your build. You may have to make adjustments to the format of the dates for it to work as expected. As you can see in the comments, we had to massage it a bit to make it work.
Using command-line JSON parser jq, as requested:
Note: Jeff Mercado's helpful answer demonstrates a lot of great advanced jq techniques, but for the specific problem at hand I believe that the text-based approach in this answer is much simpler while still being flexible enough.
#!/usr/bin/env bash
# Create variable with sample input.
IFS= read -r -d '' json <<'EOF'
[
{
"id": 3624,
"created_at": "2016-10-21T20:51:16.000+08:00"
},
{
"id": 3625,
"created_at": "2016-10-22T08:09:16.000+08:00"
},
{
"id": 3626,
"created_at": "2016-10-23T09:19:55.000+08:00"
}
]
EOF
# Use `jq` to select the objects in the array whose .created_at
# property value falls between "2016-10-21:T20:51" and "2016-10-22T08:09"
# and return them as an array (effectively a sub-array of the input).
# (To solve the problem as originally stated, simply pass "2016-10-21"
# and "2016-10-22" instead.)
jq --arg s '2016-10-21T20:51' --arg e '2016-10-22T08:09' '
map(select(.created_at | . >= $s and . <= $e + "z"))
' <<<"$json"
Arguments --arg s '2016-10-21T20:51' and --arg e '2016-10-22T08:09' define variables $s (start of date+time range) and $e (end of date+time range) respectively, for use inside the jq script.
Function map() applies the enclosed expression to all the elements of the input array and outputs the results as an array, too.
Function select() accepts a filtering expression: every input object is evaluated against the enclosed expression, and the input object is only passed out if the expression evaluates to a "truthy" value.
Expression .created_at | . >= $s and . <= $e + "z" accesses each input object's created_at property and sends its value to the comparison expression, which performs lexical comparison, which - due to the formatting of the date+time strings - amounts to chronological comparison.
Note the trailing "z" appended to the range endpoint, to ensure that it matches all date+time strings in the JSON string that prefix-match the endpoint; e.g., endpoint 2016-10-22T08:09 should match 2016-10-22T08:09:01 as well as 2016-10-22T08:59.
This lexical approach allows you to specify as many components from the beginning as desired in order to narrow or widen the date range; e.g. --arg s '2016-10-01' --arg e '2016-10-31' would match all entries for the entire month of October 2016.

Powershell Invoke-webrequest Integers not appearing

In Powershell, when I use the invoke-webrequest cmdlet in my script, I get data returned in a JSON with a variety of data types. However, integers and dates do not seem to work correctly with the convertfrom-json cmdlet.
Here is an example of the code:
$json1 = Invoke-webrequest -URI $URI -Certificate $cert -Headers $header | convertfrom-json
A field located here should have numbers in it, and they do in the response before the pipeline to convertfrom-json:
Number = $json1.workers.workAssignments.Number
So, how can I extract the number and date fields? Preferably I would like to loop through and add every employee and every attribute to a datatable, disconnected recordset, pscustomobject, or psobject & then export to CSV.
I have coded each of the above objects several different times, and none of them have I gotten the integers or dates to export correctly. So, in lieu of posting a bunch of my solutions I figured I would keep it basic with the above and ask how to get the variable out of $json1 that contains integers and into some object to export to CSV.
I am not married to convertfrom-json whatsoever.
Example JSON:
{
"workers": [ {
"associateOID": "xxxxxxxx",
"workerID": {
"idValue": "xxxxxxxx"
},
"legalName": {
"givenName": "Lebron",
"middleName": "King",
"familyName1": "James",
"formattedName": "James, Lebron King"
},
"birthDate": "1985-01-01",
"baseRemuneration": {
"payPeriodRateAmount": {
"amountValue": 25,000,000,
"currencyCode": "USD"
},
"effectiveDate": "2016-01-01"}
When using convertfrom-json, that amount in "Amount Value" results in Zero no matter what method I use.