{"id":7543,"date":"2020-06-10T14:26:50","date_gmt":"2020-06-10T18:26:50","guid":{"rendered":"https:\/\/jdhitsolutions.com\/blog\/?p=7543"},"modified":"2020-06-10T14:26:50","modified_gmt":"2020-06-10T18:26:50","slug":"solving-the-powershell-object-age-challenge-part-2","status":"publish","type":"post","link":"https:\/\/jdhitsolutions.com\/blog\/powershell\/7543\/solving-the-powershell-object-age-challenge-part-2\/","title":{"rendered":"Solving the PowerShell Object Age Challenge &#8211; Part 2"},"content":{"rendered":"<p>The other day I shared <a href=\"https:\/\/jdhitsolutions.com\/blog\/powershell\/7537\/solving-the-powershell-object-age-challenge-part-1\/\" target=\"_blank\" rel=\"noopener noreferrer\">part of my solution<\/a> to an Iron Scripter challenge to write a generic function to report on the age of an object. The idea being that you could pipe any type of object to the function and get a result. And because I can't help myself, I went a bit further with my solution.<\/p>\n<p>One of the things that nagged me about my original function, is that I needed to specify the property names for the creation and last modified time. Except, I know what those properties will be for common object types such as files and processes. So why not make it easier?<\/p>\n<h2>Using a JSON Configuration File<\/h2>\n<p>I created a JSON file with my common object types and associated properties.<\/p>\n<pre class=\"lang:json mark:0 decode:true\">[\n  {\n    \"TypeName\": \"Process\",\n    \"Created\": \"StartTime\",\n    \"Modified\": \"StartTime\",\n    \"Properties\": [\n      \"Name\",\n      \"ID\",\n      \"WS\",\n      \"Handles\"\n    ]\n  },\n  {\n    \"TypeName\": \"File\",\n    \"Created\": \"CreationTime\",\n    \"Modified\": \"LastWriteTime\",\n    \"Properties\": [\n      \"Name\",\n      \"FullName\",\n      \"Length\",\n      {\n        \"Name\" : \"SizeKB\",\n        \"Expression\" : \"[math]::Round($_.length\/1KB,2)\"\n      },\n       {\n        \"Name\" : \"Owner\",\n        \"Expression\" : \"(Get-Acl $_.fullname).owner\"\n      }\n    ]\n  },\n  {\n    \"TypeName\": \"ADUser\",\n    \"Created\": \"Created\",\n    \"Modified\": \"Modified\",\n    \"Properties\": [\n      \"DistinguishedName\",\n      \"Name\",\n      \"SAMAccountName\"\n    ]\n  },\n  {\n    \"TypeName\": \"ADGroup\",\n    \"Created\": \"Created\",\n    \"Modified\": \"Modified\",\n    \"Properties\": [\n      \"DistinguishedName\",\n      \"Name\",\n      \"GroupCategory\",\n      \"GroupScope\"\n    ]\n  },\n    {\n      \"TypeName\": \"ADComputer\",\n      \"Created\": \"Created\",\n      \"Modified\": \"Modified\",\n      \"Properties\": [\n        \"DistinguishedName\",\n        \"Name\",\n        \"DNSHostName\"\n      ]\n    }\n]\n<\/pre>\n<p>The file is called objectage-types.json. The TypeName is the identifier. I'll show you how that comes into play in a minute. Each object type has a setting for the Created and Modified properties. Process objects don't have a modified property so I'll just use the Created property. Each object type also has an array of default properties. I can even use a custom property hashtable.<\/p>\n<p>In the ps1 file, I have this line of code to run when you dot source the file.<\/p>\n<pre class=\"lang:ps mark:0 decode:true\">$global:ObjectAgeData = Get-Content $PSScriptRoot\\objectage-types.json |\nConvertFrom-Json \n<\/pre>\n<p>I would do things a bit different if this was packaged as a module.<\/p>\n<p>My parameters now include parameter sets.<\/p>\n<pre class=\"lang:ps mark:0 decode:true\">Function Get-ObjectAge2 {\n    [cmdletbinding(DefaultParameterSetName = \"custom\")]\n    [alias('goa2')]\n\n    Param(\n        [Parameter(Mandatory, ValueFromPipeline)]\n        [ValidateNotNullorEmpty()]\n        [object[]]$InputObject,\n\n        #provider a helper\n        [Parameter(Mandatory, ParameterSetName = \"named\")]\n        [ValidateSet(\"File\", \"Process\", \"ADUser\", \"ADComputer\", \"ADGroup\")]\n        [string]$ObjectType,\n\n        [Parameter(Mandatory, Position = 0, ParameterSetName = \"custom\")]\n        [string]$CreationProperty,\n\n        [Parameter(Position = 1, ParameterSetName = \"custom\")]\n        [string]$ChangeProperty,\n\n        [Parameter(ParameterSetName = \"custom\")]\n        [object[]]$Properties = @(\"Name\")\n    )\n<\/pre>\n<p>The default is what I used in the original version of this function. The other parameter set uses a parameter called ObjectType. Each item in the validation set corresponds to an object in the JSON file.<\/p>\n<p>In the function I load the data from the global variable.<\/p>\n<pre class=\"lang:ps mark:0 decode:true\">if ($pscmdlet.ParameterSetName -eq 'named') {\n    Write-Verbose \"[BEGIN  ] Using user specified object type of $ObjectType\"\n\n    #get values from the global data variable\n    $data = $global:ObjectAgeData |\n    Where-Object {$_.Typename -eq $ObjectType}\n\n    $CreationProperty = $data.Created\n    $ChangeProperty = $data.Modified\n    $Properties = $data.Properties\n\n    $tname = \"objAge.$ObjectType\"\n}\n<\/pre>\n<p>The $tname variable will be a custom object type name. You'll see how that comes into play below.<\/p>\n<h2>Creating a Custom Object<\/h2>\n<p>The process if importing each object is unchanged. I also use the ANSI progress spinner. Now for each item, I'm creating a custom property and inserting the type name.<\/p>\n<pre class=\"lang:ps mark:0 decode:true\">foreach ($item in $all) {\n    $Age = New-TimeSpan -end $Now -Start ($item.$creationProperty)\n    $tmpHash = [ordered]@{\n        PSTypeName = $tname\n        Created    = $item.$CreationProperty\n        Modified   = $item.$ChangeProperty\n        Age        = $Age\n        Average    = If ($Age -gt $Avg) {\"Above\"} else {\"Below\"}\n        AverageAll = $avg\n    }\n            \n    foreach ($prop in $Properties) {\n        if ($prop.gettype().name -eq 'hashtable') {\n                Write-Verbose \"[END    ] Expanding hashtable\"\n                $custom = $item | Select-object -property $prop\n                $prop = $custom.psobject.properties.Name\n                $val = $custom.psobject.properties.Value\n        }\n        elseif ($prop.gettype().name -eq \"PSCustomObject\") {\n                Write-Verbose \"[END    ] Expanding custom object probably from a json file\"\n                #reconstruct the hashtable\n                $tmpProp = @{Name = \"$($prop.name)\";Expression = $([scriptblock]::create($prop.Expression))}\n                $custom = $item | Select-object -property $tmpProp\n                $prop = $custom.psobject.properties.Name\n                $val = $custom.psobject.properties.Value\n        }\n        else {                \n            $val = $item.$prop\n        }\n            Write-Verbose \"[END    ] Adding property $prop $val\"\n            $tmpHash.Add($prop, $val)\n    } #foreach prop\n\n    #create the object\n    New-Object -TypeName PSObject -property $tmpHash\n\n} #foreach item\n<\/pre>\n<p>I had to add some code to recreate property hashtables from the JSON file since everything is treated as a string.<\/p>\n<p>The reason I added a new custom type name, is so that I can format the results. At least by default. I built a format.ps1xml file using the New-PSFormatXML command from the PSScriptTools module.<\/p>\n<pre class=\"lang:xml mark:0 decode:true\">&lt;?xml version=\"1.0\" encoding=\"UTF-8\"?&gt;\n&lt;!--\nformat type data generated 05\/26\/2020 15:50:58 by BOVINE320\\Jeff\nThis file is incomplete. It doesn't have all of the defined\nobject types in Get-ObjectAge2. Consider this file a proof of\nconcept or work-in-progress.\n--&gt;\n&lt;Configuration&gt;\n  &lt;ViewDefinitions&gt;\n    &lt;View&gt;\n      &lt;!--Created 05\/26\/2020 15:50:58 by BOVINE320\\Jeff--&gt;\n      &lt;Name&gt;default&lt;\/Name&gt;\n      &lt;ViewSelectedBy&gt;\n        &lt;TypeName&gt;objAge.Process&lt;\/TypeName&gt;\n      &lt;\/ViewSelectedBy&gt;\n      &lt;TableControl&gt;\n        &lt;!--Delete the AutoSize node if you want to use the defined widths.\n        &lt;AutoSize \/&gt;--&gt;\n        &lt;TableHeaders&gt;\n          &lt;TableColumnHeader&gt;\n            &lt;Label&gt;Created&lt;\/Label&gt;\n            &lt;Width&gt;21&lt;\/Width&gt;\n            &lt;Alignment&gt;left&lt;\/Alignment&gt;\n          &lt;\/TableColumnHeader&gt;\n          &lt;TableColumnHeader&gt;\n            &lt;Label&gt;Modified&lt;\/Label&gt;\n            &lt;Width&gt;21&lt;\/Width&gt;\n            &lt;Alignment&gt;left&lt;\/Alignment&gt;\n          &lt;\/TableColumnHeader&gt;\n          &lt;TableColumnHeader&gt;\n            &lt;Label&gt;Age&lt;\/Label&gt;\n            &lt;Width&gt;11&lt;\/Width&gt;\n            &lt;Alignment&gt;right&lt;\/Alignment&gt;\n          &lt;\/TableColumnHeader&gt;\n          &lt;TableColumnHeader&gt;\n            &lt;Label&gt;Avg&lt;\/Label&gt;\n            &lt;Width&gt;5&lt;\/Width&gt;\n            &lt;Alignment&gt;left&lt;\/Alignment&gt;\n          &lt;\/TableColumnHeader&gt;\n          &lt;TableColumnHeader&gt;\n            &lt;Label&gt;Name&lt;\/Label&gt;\n            &lt;Width&gt;23&lt;\/Width&gt;\n            &lt;Alignment&gt;left&lt;\/Alignment&gt;\n          &lt;\/TableColumnHeader&gt;\n          &lt;TableColumnHeader&gt;\n            &lt;Label&gt;ID&lt;\/Label&gt;\n            &lt;Width&gt;7&lt;\/Width&gt;\n            &lt;Alignment&gt;left&lt;\/Alignment&gt;\n          &lt;\/TableColumnHeader&gt;\n          &lt;TableColumnHeader&gt;\n            &lt;Label&gt;WS&lt;\/Label&gt;\n            &lt;Width&gt;11&lt;\/Width&gt;\n            &lt;Alignment&gt;left&lt;\/Alignment&gt;\n          &lt;\/TableColumnHeader&gt;\n          &lt;TableColumnHeader&gt;\n            &lt;Label&gt;Handles&lt;\/Label&gt;\n            &lt;Width&gt;10&lt;\/Width&gt;\n            &lt;Alignment&gt;left&lt;\/Alignment&gt;\n          &lt;\/TableColumnHeader&gt;\n        &lt;\/TableHeaders&gt;\n        &lt;TableRowEntries&gt;\n          &lt;TableRowEntry&gt;\n            &lt;TableColumnItems&gt;\n              &lt;!--\n            By default the entries use property names, but you can replace them with scriptblocks.\n            &lt;ScriptBlock&gt;$_.foo \/1mb -as [int]&lt;\/ScriptBlock&gt;\n--&gt;\n              &lt;TableColumnItem&gt;\n                &lt;PropertyName&gt;Created&lt;\/PropertyName&gt;\n              &lt;\/TableColumnItem&gt;\n              &lt;TableColumnItem&gt;\n                &lt;PropertyName&gt;Modified&lt;\/PropertyName&gt;\n              &lt;\/TableColumnItem&gt;\n              &lt;TableColumnItem&gt;\n                &lt;ScriptBlock&gt;\n                $_.Age.ToString(\"d'.'hh':'mm':'ss\")\n                &lt;\/ScriptBlock&gt;\n              &lt;\/TableColumnItem&gt;\n              &lt;TableColumnItem&gt;\n                &lt;ScriptBlock&gt;\n\n                if ($_.Average -eq 'Above') {\n                  \"$([char]0x1b)[91m$($_.average)$([char]0x1b)[0m\"\n                }\n                else {\n                  $_.Average\n                }\n                &lt;\/ScriptBlock&gt;\n              &lt;\/TableColumnItem&gt;\n              &lt;TableColumnItem&gt;\n                &lt;PropertyName&gt;Name&lt;\/PropertyName&gt;\n              &lt;\/TableColumnItem&gt;\n              &lt;TableColumnItem&gt;\n                &lt;PropertyName&gt;ID&lt;\/PropertyName&gt;\n              &lt;\/TableColumnItem&gt;\n              &lt;TableColumnItem&gt;\n                &lt;PropertyName&gt;WS&lt;\/PropertyName&gt;\n              &lt;\/TableColumnItem&gt;\n              &lt;TableColumnItem&gt;\n                &lt;PropertyName&gt;Handles&lt;\/PropertyName&gt;\n              &lt;\/TableColumnItem&gt;\n            &lt;\/TableColumnItems&gt;\n          &lt;\/TableRowEntry&gt;\n        &lt;\/TableRowEntries&gt;\n      &lt;\/TableControl&gt;\n    &lt;\/View&gt;\n    &lt;View&gt;\n      &lt;!--Created 05\/26\/2020 16:01:55 by BOVINE320\\Jeff--&gt;\n      &lt;Name&gt;default&lt;\/Name&gt;\n      &lt;ViewSelectedBy&gt;\n        &lt;TypeName&gt;objAge.File&lt;\/TypeName&gt;\n      &lt;\/ViewSelectedBy&gt;\n      &lt;GroupBy&gt;\n        &lt;PropertyName&gt;AverageAll&lt;\/PropertyName&gt;\n        &lt;Label&gt;Overall Average Creation Age&lt;\/Label&gt;\n      &lt;\/GroupBy&gt;\n      &lt;TableControl&gt;\n        &lt;!--Delete the AutoSize node if you want to use the defined widths.\n        &lt;AutoSize \/&gt;--&gt;\n        &lt;TableHeaders&gt;\n          &lt;TableColumnHeader&gt;\n            &lt;Label&gt;Created&lt;\/Label&gt;\n            &lt;Width&gt;24&lt;\/Width&gt;\n            &lt;Alignment&gt;left&lt;\/Alignment&gt;\n          &lt;\/TableColumnHeader&gt;\n          &lt;TableColumnHeader&gt;\n            &lt;Label&gt;Modified&lt;\/Label&gt;\n            &lt;Width&gt;23&lt;\/Width&gt;\n            &lt;Alignment&gt;left&lt;\/Alignment&gt;\n          &lt;\/TableColumnHeader&gt;\n          &lt;TableColumnHeader&gt;\n            &lt;Label&gt;Age&lt;\/Label&gt;\n            &lt;Width&gt;15&lt;\/Width&gt;\n            &lt;Alignment&gt;Right&lt;\/Alignment&gt;\n          &lt;\/TableColumnHeader&gt;\n          &lt;TableColumnHeader&gt;\n            &lt;Label&gt;Average&lt;\/Label&gt;\n            &lt;Width&gt;10&lt;\/Width&gt;\n            &lt;Alignment&gt;center&lt;\/Alignment&gt;\n          &lt;\/TableColumnHeader&gt;\n          &lt;TableColumnHeader&gt;\n            &lt;Label&gt;Name&lt;\/Label&gt;\n            &lt;Width&gt;24&lt;\/Width&gt;\n            &lt;Alignment&gt;left&lt;\/Alignment&gt;\n          &lt;\/TableColumnHeader&gt;\n          &lt;TableColumnHeader&gt;\n            &lt;Label&gt;SizeKB&lt;\/Label&gt;\n            &lt;Width&gt;9&lt;\/Width&gt;\n            &lt;Alignment&gt;right&lt;\/Alignment&gt;\n          &lt;\/TableColumnHeader&gt;\n        &lt;\/TableHeaders&gt;\n        &lt;TableRowEntries&gt;\n          &lt;TableRowEntry&gt;\n            &lt;TableColumnItems&gt;\n              &lt;!--\n            By default the entries use property names, but you can replace them with scriptblocks.\n            &lt;ScriptBlock&gt;$_.foo \/1mb -as [int]&lt;\/ScriptBlock&gt;\n--&gt;\n              &lt;TableColumnItem&gt;\n                &lt;PropertyName&gt;Created&lt;\/PropertyName&gt;\n              &lt;\/TableColumnItem&gt;\n              &lt;TableColumnItem&gt;\n                &lt;PropertyName&gt;Modified&lt;\/PropertyName&gt;\n              &lt;\/TableColumnItem&gt;\n              &lt;TableColumnItem&gt;\n                &lt;ScriptBlock&gt;\n                $_.Age.ToString(\"d'.'hh':'mm':'ss\")\n                &lt;\/ScriptBlock&gt;\n              &lt;\/TableColumnItem&gt;\n              &lt;TableColumnItem&gt;\n                &lt;ScriptBlock&gt;\n                if ($_.Average -eq 'Above') {\n                  \"$([char]0x1b)[91m$($_.average)$([char]0x1b)[0m\"\n                }\n                else {\n                  $_.Average\n                }\n                &lt;\/ScriptBlock&gt;\n              &lt;\/TableColumnItem&gt;\n              &lt;TableColumnItem&gt;\n                &lt;PropertyName&gt;Name&lt;\/PropertyName&gt;\n              &lt;\/TableColumnItem&gt;\n              &lt;TableColumnItem&gt;\n                &lt;PropertyName&gt;SizeKB&lt;\/PropertyName&gt;\n              &lt;\/TableColumnItem&gt;\n            &lt;\/TableColumnItems&gt;\n          &lt;\/TableRowEntry&gt;\n        &lt;\/TableRowEntries&gt;\n      &lt;\/TableControl&gt;\n    &lt;\/View&gt;\n    &lt;View&gt;\n      &lt;!--Created 05\/26\/2020 16:09:07 by BOVINE320\\Jeff--&gt;\n      &lt;Name&gt;default&lt;\/Name&gt;\n      &lt;ViewSelectedBy&gt;\n        &lt;TypeName&gt;objAge.ADUser&lt;\/TypeName&gt;\n      &lt;\/ViewSelectedBy&gt;\n      &lt;GroupBy&gt;\n        &lt;PropertyName&gt;AverageAll&lt;\/PropertyName&gt;\n        &lt;Label&gt;Overall Average Creation Age&lt;\/Label&gt;\n      &lt;\/GroupBy&gt;\n      &lt;TableControl&gt;\n        &lt;!--Delete the AutoSize node if you want to use the defined widths.\n        &lt;AutoSize \/&gt;--&gt;\n        &lt;TableHeaders&gt;\n          &lt;TableColumnHeader&gt;\n            &lt;Label&gt;Created&lt;\/Label&gt;\n            &lt;Width&gt;23&lt;\/Width&gt;\n            &lt;Alignment&gt;left&lt;\/Alignment&gt;\n          &lt;\/TableColumnHeader&gt;\n          &lt;TableColumnHeader&gt;\n            &lt;Label&gt;Modified&lt;\/Label&gt;\n            &lt;Width&gt;22&lt;\/Width&gt;\n            &lt;Alignment&gt;left&lt;\/Alignment&gt;\n          &lt;\/TableColumnHeader&gt;\n          &lt;TableColumnHeader&gt;\n            &lt;Label&gt;Age&lt;\/Label&gt;\n            &lt;Width&gt;15&lt;\/Width&gt;\n            &lt;Alignment&gt;right&lt;\/Alignment&gt;\n          &lt;\/TableColumnHeader&gt;\n          &lt;TableColumnHeader&gt;\n            &lt;Label&gt;Avg&lt;\/Label&gt;\n            &lt;Width&gt;5&lt;\/Width&gt;\n            &lt;Alignment&gt;left&lt;\/Alignment&gt;\n          &lt;\/TableColumnHeader&gt;\n          &lt;TableColumnHeader&gt;\n            &lt;Label&gt;DistinguishedName&lt;\/Label&gt;\n            &lt;Width&gt;46&lt;\/Width&gt;\n            &lt;Alignment&gt;left&lt;\/Alignment&gt;\n          &lt;\/TableColumnHeader&gt;\n        &lt;\/TableHeaders&gt;\n        &lt;TableRowEntries&gt;\n          &lt;TableRowEntry&gt;\n            &lt;TableColumnItems&gt;\n              &lt;!--\n            By default the entries use property names, but you can replace them with scriptblocks.\n            &lt;ScriptBlock&gt;$_.foo \/1mb -as [int]&lt;\/ScriptBlock&gt;\n--&gt;\n              &lt;TableColumnItem&gt;\n                &lt;PropertyName&gt;Created&lt;\/PropertyName&gt;\n              &lt;\/TableColumnItem&gt;\n              &lt;TableColumnItem&gt;\n                &lt;PropertyName&gt;Modified&lt;\/PropertyName&gt;\n              &lt;\/TableColumnItem&gt;\n              &lt;TableColumnItem&gt;\n                &lt;ScriptBlock&gt;\n                $_.Age.ToString(\"d'.'hh':'mm':'ss\")\n                &lt;\/ScriptBlock&gt;\n              &lt;\/TableColumnItem&gt;\n              &lt;TableColumnItem&gt;\n                &lt;ScriptBlock&gt;\n                if ($_.Average -eq 'Above') {\n                  \"$([char]0x1b)[91m$($_.average)$([char]0x1b)[0m\"\n                }\n                else {\n                  $_.Average\n                }\n                &lt;\/ScriptBlock&gt;\n              &lt;\/TableColumnItem&gt;\n              &lt;TableColumnItem&gt;\n                &lt;PropertyName&gt;DistinguishedName&lt;\/PropertyName&gt;\n              &lt;\/TableColumnItem&gt;\n            &lt;\/TableColumnItems&gt;\n          &lt;\/TableRowEntry&gt;\n        &lt;\/TableRowEntries&gt;\n      &lt;\/TableControl&gt;\n    &lt;\/View&gt;\n  &lt;\/ViewDefinitions&gt;\n&lt;\/Configuration&gt;\n<\/pre>\n<p>I load this file in the .ps1 file.<\/p>\n<pre class=\"lang:ps mark:0 decode:true\">Update-FormatData $PSScriptRoot\\objage.format.ps1xml\n<\/pre>\n<p>Now I can easily get formatted results for my common object types.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-7545\" src=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage2-process.png\" alt=\"get-objectage2-process\" width=\"1034\" height=\"306\" srcset=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage2-process.png 1034w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage2-process-300x89.png 300w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage2-process-1024x303.png 1024w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage2-process-768x227.png 768w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage2-process-850x252.png 850w\" sizes=\"auto, (max-width: 1034px) 100vw, 1034px\" \/>I almost forgot. My format file includes ANSI formatting for the Avg property.<\/p>\n<p>When querying Active Directory, I have to remember to include the necessary properties.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-7546\" src=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage2-aduser.png\" alt=\"get-objectage2-aduser\" width=\"1050\" height=\"325\" srcset=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage2-aduser.png 1050w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage2-aduser-300x93.png 300w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage2-aduser-1024x317.png 1024w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage2-aduser-768x238.png 768w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage2-aduser-850x263.png 850w\" sizes=\"auto, (max-width: 1050px) 100vw, 1050px\" \/> For this object I added a custom GroupBy property to use the AverageAll value. That property is part of the custom object.<\/p>\n<p>And I can still use the custom way for everything else.<\/p>\n<pre class=\"lang:ps mark:0 decode:true\">$cim = @{\nNamespace  = \"root\/virtualization\/v2\"\nClassName  = \"Msvm_ComputerSystem\"\nfilter = \"caption='Virtual Machine'\"\n}\n$goa = @{\n CreationProperty = \"InstallDate\"\n ChangeProperty = \"TimeOfLastStateChange\"\n Properties = @{Name=\"VM\";Expression={$_.ElementName}},\n @{Name=\"Uptime\";Expression = {New-TimeSpan -Start (Get-Date).AddMilliseconds(-$_.ontimeInMilliseconds) -End (Get-Date)}}\n}\n\nGet-Ciminstance @cim | Get-ObjectAge2 @goa\n<\/pre>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-7547\" src=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage2-vm.png\" alt=\"get-objectage2-vm\" width=\"1101\" height=\"639\" srcset=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage2-vm.png 1101w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage2-vm-300x174.png 300w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage2-vm-1024x594.png 1024w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage2-vm-768x446.png 768w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage2-vm-850x493.png 850w\" sizes=\"auto, (max-width: 1101px) 100vw, 1101px\" \/><\/p>\n<h2>The Code<\/h2>\n<p>Here's the complete function and relevant commands.<\/p>\n<pre class=\"lang:ps mark:0 decode:true\">Function Get-ObjectAge2 {\n    [cmdletbinding(DefaultParameterSetName = \"custom\")]\n    [alias('goa2')]\n\n    Param(\n        [Parameter(Mandatory, ValueFromPipeline)]\n        [ValidateNotNullorEmpty()]\n        [object[]]$InputObject,\n\n        #provider a helper\n        [Parameter(Mandatory, ParameterSetName = \"named\")]\n        [ValidateSet(\"File\", \"Process\", \"ADUser\", \"ADComputer\", \"ADGroup\")]\n        [string]$ObjectType,\n\n        [Parameter(Mandatory, Position = 0, ParameterSetName = \"custom\")]\n        [string]$CreationProperty,\n\n        [Parameter(Position = 1, ParameterSetName = \"custom\")]\n        [string]$ChangeProperty,\n\n        [Parameter(ParameterSetName = \"custom\")]\n        [object[]]$Properties = @(\"Name\")\n    )\n\n    Begin {\n        Write-Verbose \"[BEGIN  ] Starting: $($MyInvocation.Mycommand)\"\n        $all = [System.Collections.Generic.List[object]]::new()\n\n        if ($pscmdlet.ParameterSetName -eq 'named') {\n            Write-Verbose \"[BEGIN  ] Using user specified object type of $ObjectType\"\n\n            #get values from the global data variable\n            $data = $global:ObjectAgeData |\n            Where-Object {$_.Typename -eq $ObjectType}\n\n            $CreationProperty = $data.Created\n            $ChangeProperty = $data.Modified\n            $Properties = $data.Properties\n\n            $tname = \"objAge.$ObjectType\"\n        }\n        else {\n            #set a default value\n            $tname = \"objAge\"\n        }\n\n        #use the same date time for all timespans\n        $Now = Get-Date\n        Write-Verbose \"[BEGIN  ] Using $NOW for timespan calculations\"\n        Write-Verbose \"[BEGIN  ] Using $CreationProperty for CreationProperty\"\n        #use CreationProperty parameter for Changed property if latter is not specified\n        if (-Not $ChangeProperty) {\n            Write-Verbose \"[BEGIN  ] Using $CreationProperty for ChangeProperty\"\n            $changeProperty = $CreationProperty\n        }\n        else {\n            Write-Verbose \"[BEGIN  ] Using $ChangeProperty for ChangeProperty\"\n        }\n\n        #initialize counters and an array of characters to be using in a progress wheel\n        $i = 0\n        $ch = @(\"|\", \"\/\", \"-\", \"\\\", \"|\", \"\/\", \"-\")\n        $c = 0\n\n    } #begin\n\n    Process {\n        foreach ($object in $InputObject) {\n            $i++\n            #display a progress symbol if NOT using -Verbose\n            if (-Not ($VerbosePreference -eq \"Continue\")) {\n                if ($c -ge $ch.count) {\n                    $c = 0\n                }\n                Write-Host \"$([char]0x1b)[1EProcessing $($ch[$c])$([char]0x1b)[0m\" -ForegroundColor cyan -NoNewline\n                $c++\n                Start-Sleep -Milliseconds 10\n            }\n            $all.Add($object)\n        }\n    } #process\n\n    End {\n        Write-Verbose \"[END    ] Calculating average creation age for $($all.count) objects\"\n        $allTimeSpans = $all | ForEach-Object {\n            Try {\n                $X = $_\n                New-TimeSpan -start $_.$creationProperty -end $Now -ErrorAction Stop\n            }\n            Catch {\n                Write-Warning \"Failed to get $CreationProperty value. $($_.exception.message)\"\n                Write-Warning ($X | Out-String)\n            }\n        }  #foreach\n\n        $avg = New-TimeSpan -seconds ($allTimeSpans | Measure-Object -Property TotalSeconds -average).Average\n\n        $results = foreach ($item in $all) {\n            $Age = New-TimeSpan -end $Now -Start ($item.$creationProperty)\n            $tmpHash = [ordered]@{\n                PSTypeName = $tname\n                Created    = $item.$CreationProperty\n                Modified   = $item.$ChangeProperty\n                Age        = $Age\n                Average    = If ($Age -gt $Avg) {\"Above\"} else {\"Below\"}\n                AverageAll = $avg\n            }\n            \n            foreach ($prop in $Properties) {\n                if ($prop.gettype().name -eq 'hashtable') {\n                        Write-Verbose \"[END    ] Expanding hashtable\"\n                        $custom = $item | Select-object -property $prop\n                        $prop = $custom.psobject.properties.Name\n                        $val = $custom.psobject.properties.Value\n                }\n                elseif ($prop.gettype().name -eq \"PSCustomObject\") {\n                     Write-Verbose \"[END    ] Expanding custom object probably from a json file\"\n                     #reconstruct the hashtable\n                     $tmpProp = @{Name = \"$($prop.name)\";Expression = $([scriptblock]::create($prop.Expression))}\n                     $custom = $item | Select-object -property $tmpProp\n                     $prop = $custom.psobject.properties.Name\n                     $val = $custom.psobject.properties.Value\n                }\n                else {                \n                    $val = $item.$prop\n                }\n                  Write-Verbose \"[END    ] Adding property $prop $val\"\n                  $tmpHash.Add($prop, $val)\n            } #foreach prop\n\n            #create the object\n            New-Object -TypeName PSObject -property $tmpHash\n\n        } #foreach item\n\n        $results\n        Write-Verbose \"[END    ] Ending: $($MyInvocation.Mycommand)\"\n    } #end\n\n} #end function\n\n#define a global variable with type information\n$global:ObjectAgeData = Get-Content $PSScriptRoot\\objectage-types.json | ConvertFrom-Json \n\n#load the formatting file\nUpdate-FormatData $PSScriptRoot\\objage.format.ps1xml\n<\/pre>\n<p>All of this should be considered a proof-of-concept. But I hope you'll try it out. Or at least look at what I wrote and why. There are a number of moving parts here. Make sure you understand how this all works. If you have any questions, please feel free to leave a comment.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The other day I shared part of my solution to an Iron Scripter challenge to write a generic function to report on the age of an object. The idea being that you could pipe any type of object to the function and get a result. And because I can&#8217;t help myself, I went a bit&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"just posted: Solving the #PowerShell Object Age Challenge - Part 2","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[4,8],"tags":[628,224,618,411,534,540,391],"class_list":["post-7543","post","type-post","status-publish","format-standard","hentry","category-powershell","category-scripting","tag-ansi","tag-function","tag-iron-scripter","tag-json","tag-powershell","tag-scripting","tag-update-formatdata"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.6 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Solving the PowerShell Object Age Challenge - Part 2 &#8226; The Lonely Administrator<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/jdhitsolutions.com\/blog\/powershell\/7543\/solving-the-powershell-object-age-challenge-part-2\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Solving the PowerShell Object Age Challenge - Part 2 &#8226; The Lonely Administrator\" \/>\n<meta property=\"og:description\" content=\"The other day I shared part of my solution to an Iron Scripter challenge to write a generic function to report on the age of an object. The idea being that you could pipe any type of object to the function and get a result. And because I can&#039;t help myself, I went a bit...\" \/>\n<meta property=\"og:url\" content=\"https:\/\/jdhitsolutions.com\/blog\/powershell\/7543\/solving-the-powershell-object-age-challenge-part-2\/\" \/>\n<meta property=\"og:site_name\" content=\"The Lonely Administrator\" \/>\n<meta property=\"article:published_time\" content=\"2020-06-10T18:26:50+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage2-process.png\" \/>\n<meta name=\"author\" content=\"Jeffery Hicks\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@JeffHicks\" \/>\n<meta name=\"twitter:site\" content=\"@JeffHicks\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Jeffery Hicks\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"13 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/powershell\\\/7543\\\/solving-the-powershell-object-age-challenge-part-2\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/powershell\\\/7543\\\/solving-the-powershell-object-age-challenge-part-2\\\/\"},\"author\":{\"name\":\"Jeffery Hicks\",\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/#\\\/schema\\\/person\\\/d0258030b41f07fd745f4078bdf5b6c9\"},\"headline\":\"Solving the PowerShell Object Age Challenge &#8211; Part 2\",\"datePublished\":\"2020-06-10T18:26:50+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/powershell\\\/7543\\\/solving-the-powershell-object-age-challenge-part-2\\\/\"},\"wordCount\":543,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/#\\\/schema\\\/person\\\/d0258030b41f07fd745f4078bdf5b6c9\"},\"image\":{\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/powershell\\\/7543\\\/solving-the-powershell-object-age-challenge-part-2\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/wp-content\\\/uploads\\\/2020\\\/06\\\/get-objectage2-process.png\",\"keywords\":[\"ANSI\",\"Function\",\"Iron Scripter\",\"JSON\",\"PowerShell\",\"Scripting\",\"Update-FormatData\"],\"articleSection\":[\"PowerShell\",\"Scripting\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/powershell\\\/7543\\\/solving-the-powershell-object-age-challenge-part-2\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/powershell\\\/7543\\\/solving-the-powershell-object-age-challenge-part-2\\\/\",\"url\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/powershell\\\/7543\\\/solving-the-powershell-object-age-challenge-part-2\\\/\",\"name\":\"Solving the PowerShell Object Age Challenge - Part 2 &#8226; The Lonely Administrator\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/powershell\\\/7543\\\/solving-the-powershell-object-age-challenge-part-2\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/powershell\\\/7543\\\/solving-the-powershell-object-age-challenge-part-2\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/wp-content\\\/uploads\\\/2020\\\/06\\\/get-objectage2-process.png\",\"datePublished\":\"2020-06-10T18:26:50+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/powershell\\\/7543\\\/solving-the-powershell-object-age-challenge-part-2\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/powershell\\\/7543\\\/solving-the-powershell-object-age-challenge-part-2\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/powershell\\\/7543\\\/solving-the-powershell-object-age-challenge-part-2\\\/#primaryimage\",\"url\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/wp-content\\\/uploads\\\/2020\\\/06\\\/get-objectage2-process.png\",\"contentUrl\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/wp-content\\\/uploads\\\/2020\\\/06\\\/get-objectage2-process.png\",\"width\":1034,\"height\":306},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/powershell\\\/7543\\\/solving-the-powershell-object-age-challenge-part-2\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"PowerShell\",\"item\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/category\\\/powershell\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Solving the PowerShell Object Age Challenge &#8211; Part 2\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/\",\"name\":\"The Lonely Administrator\",\"description\":\"Practical Advice for the Automating IT Pro\",\"publisher\":{\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/#\\\/schema\\\/person\\\/d0258030b41f07fd745f4078bdf5b6c9\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/#\\\/schema\\\/person\\\/d0258030b41f07fd745f4078bdf5b6c9\",\"name\":\"Jeffery Hicks\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/832ae5d438fdcfc1420d720cd1991307927de8a0b12f2342e81c30f773e21098?s=96&d=wavatar&r=pg\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/832ae5d438fdcfc1420d720cd1991307927de8a0b12f2342e81c30f773e21098?s=96&d=wavatar&r=pg\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/832ae5d438fdcfc1420d720cd1991307927de8a0b12f2342e81c30f773e21098?s=96&d=wavatar&r=pg\",\"caption\":\"Jeffery Hicks\"},\"logo\":{\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/832ae5d438fdcfc1420d720cd1991307927de8a0b12f2342e81c30f773e21098?s=96&d=wavatar&r=pg\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Solving the PowerShell Object Age Challenge - Part 2 &#8226; The Lonely Administrator","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/jdhitsolutions.com\/blog\/powershell\/7543\/solving-the-powershell-object-age-challenge-part-2\/","og_locale":"en_US","og_type":"article","og_title":"Solving the PowerShell Object Age Challenge - Part 2 &#8226; The Lonely Administrator","og_description":"The other day I shared part of my solution to an Iron Scripter challenge to write a generic function to report on the age of an object. The idea being that you could pipe any type of object to the function and get a result. And because I can't help myself, I went a bit...","og_url":"https:\/\/jdhitsolutions.com\/blog\/powershell\/7543\/solving-the-powershell-object-age-challenge-part-2\/","og_site_name":"The Lonely Administrator","article_published_time":"2020-06-10T18:26:50+00:00","og_image":[{"url":"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage2-process.png","type":"","width":"","height":""}],"author":"Jeffery Hicks","twitter_card":"summary_large_image","twitter_creator":"@JeffHicks","twitter_site":"@JeffHicks","twitter_misc":{"Written by":"Jeffery Hicks","Est. reading time":"13 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/jdhitsolutions.com\/blog\/powershell\/7543\/solving-the-powershell-object-age-challenge-part-2\/#article","isPartOf":{"@id":"https:\/\/jdhitsolutions.com\/blog\/powershell\/7543\/solving-the-powershell-object-age-challenge-part-2\/"},"author":{"name":"Jeffery Hicks","@id":"https:\/\/jdhitsolutions.com\/blog\/#\/schema\/person\/d0258030b41f07fd745f4078bdf5b6c9"},"headline":"Solving the PowerShell Object Age Challenge &#8211; Part 2","datePublished":"2020-06-10T18:26:50+00:00","mainEntityOfPage":{"@id":"https:\/\/jdhitsolutions.com\/blog\/powershell\/7543\/solving-the-powershell-object-age-challenge-part-2\/"},"wordCount":543,"commentCount":0,"publisher":{"@id":"https:\/\/jdhitsolutions.com\/blog\/#\/schema\/person\/d0258030b41f07fd745f4078bdf5b6c9"},"image":{"@id":"https:\/\/jdhitsolutions.com\/blog\/powershell\/7543\/solving-the-powershell-object-age-challenge-part-2\/#primaryimage"},"thumbnailUrl":"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage2-process.png","keywords":["ANSI","Function","Iron Scripter","JSON","PowerShell","Scripting","Update-FormatData"],"articleSection":["PowerShell","Scripting"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/jdhitsolutions.com\/blog\/powershell\/7543\/solving-the-powershell-object-age-challenge-part-2\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/jdhitsolutions.com\/blog\/powershell\/7543\/solving-the-powershell-object-age-challenge-part-2\/","url":"https:\/\/jdhitsolutions.com\/blog\/powershell\/7543\/solving-the-powershell-object-age-challenge-part-2\/","name":"Solving the PowerShell Object Age Challenge - Part 2 &#8226; The Lonely Administrator","isPartOf":{"@id":"https:\/\/jdhitsolutions.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/jdhitsolutions.com\/blog\/powershell\/7543\/solving-the-powershell-object-age-challenge-part-2\/#primaryimage"},"image":{"@id":"https:\/\/jdhitsolutions.com\/blog\/powershell\/7543\/solving-the-powershell-object-age-challenge-part-2\/#primaryimage"},"thumbnailUrl":"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage2-process.png","datePublished":"2020-06-10T18:26:50+00:00","breadcrumb":{"@id":"https:\/\/jdhitsolutions.com\/blog\/powershell\/7543\/solving-the-powershell-object-age-challenge-part-2\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/jdhitsolutions.com\/blog\/powershell\/7543\/solving-the-powershell-object-age-challenge-part-2\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/jdhitsolutions.com\/blog\/powershell\/7543\/solving-the-powershell-object-age-challenge-part-2\/#primaryimage","url":"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage2-process.png","contentUrl":"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage2-process.png","width":1034,"height":306},{"@type":"BreadcrumbList","@id":"https:\/\/jdhitsolutions.com\/blog\/powershell\/7543\/solving-the-powershell-object-age-challenge-part-2\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"PowerShell","item":"https:\/\/jdhitsolutions.com\/blog\/category\/powershell\/"},{"@type":"ListItem","position":2,"name":"Solving the PowerShell Object Age Challenge &#8211; Part 2"}]},{"@type":"WebSite","@id":"https:\/\/jdhitsolutions.com\/blog\/#website","url":"https:\/\/jdhitsolutions.com\/blog\/","name":"The Lonely Administrator","description":"Practical Advice for the Automating IT Pro","publisher":{"@id":"https:\/\/jdhitsolutions.com\/blog\/#\/schema\/person\/d0258030b41f07fd745f4078bdf5b6c9"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/jdhitsolutions.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/jdhitsolutions.com\/blog\/#\/schema\/person\/d0258030b41f07fd745f4078bdf5b6c9","name":"Jeffery Hicks","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/832ae5d438fdcfc1420d720cd1991307927de8a0b12f2342e81c30f773e21098?s=96&d=wavatar&r=pg","url":"https:\/\/secure.gravatar.com\/avatar\/832ae5d438fdcfc1420d720cd1991307927de8a0b12f2342e81c30f773e21098?s=96&d=wavatar&r=pg","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/832ae5d438fdcfc1420d720cd1991307927de8a0b12f2342e81c30f773e21098?s=96&d=wavatar&r=pg","caption":"Jeffery Hicks"},"logo":{"@id":"https:\/\/secure.gravatar.com\/avatar\/832ae5d438fdcfc1420d720cd1991307927de8a0b12f2342e81c30f773e21098?s=96&d=wavatar&r=pg"}}]}},"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_likes_enabled":true,"jetpack-related-posts":[{"id":8107,"url":"https:\/\/jdhitsolutions.com\/blog\/powershell\/8107\/scripting-challenge-meetup\/","url_meta":{"origin":7543,"position":0},"title":"Scripting Challenge Meetup","author":"Jeffery Hicks","date":"February 1, 2021","format":false,"excerpt":"As you probably know, I am the PowerShell problem master behind the challenges from the Iron Scripter site. Solving a PowerShell scripting challenge is a great way to test your skills and expand your knowledge. The final result is merely a means to an end. How you get there and\u2026","rel":"","context":"In &quot;PowerShell&quot;","block_context":{"text":"PowerShell","link":"https:\/\/jdhitsolutions.com\/blog\/category\/powershell\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/02\/rubik.jpg?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":7024,"url":"https:\/\/jdhitsolutions.com\/blog\/powershell\/7024\/managing-the-recycle-bin-with-powershell\/","url_meta":{"origin":7543,"position":1},"title":"Managing the Recycle Bin with PowerShell","author":"Jeffery Hicks","date":"December 3, 2019","format":false,"excerpt":"A while ago, I posted an Iron Scripter challenge asking you to write some PowerShell code for working with items in the recycle bin. You were asked to calculate how much space the recycle bin is using and then restore a file. If you'd prefer, stop reading this post, check\u2026","rel":"","context":"In &quot;PowerShell&quot;","block_context":{"text":"PowerShell","link":"https:\/\/jdhitsolutions.com\/blog\/category\/powershell\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2019\/12\/image_thumb-7.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2019\/12\/image_thumb-7.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2019\/12\/image_thumb-7.png?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2019\/12\/image_thumb-7.png?resize=700%2C400&ssl=1 2x"},"classes":[]},{"id":5319,"url":"https:\/\/jdhitsolutions.com\/blog\/powershell\/5319\/a-classy-christmas-powershell-module\/","url_meta":{"origin":7543,"position":2},"title":"A Classy Christmas PowerShell Module","author":"Jeffery Hicks","date":"December 20, 2016","format":false,"excerpt":"Yesterday I showed you a class-based PowerShell script. My intention was to have a little bit of fun and teach you the basics of using a class. But what I gave you was really just the first step. If you wanted to create an actual tool around a class, you\u2026","rel":"","context":"In &quot;PowerShell&quot;","block_context":{"text":"PowerShell","link":"https:\/\/jdhitsolutions.com\/blog\/category\/powershell\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2016\/12\/christmas-wreath13.png?resize=350%2C200","width":350,"height":200},"classes":[]},{"id":7537,"url":"https:\/\/jdhitsolutions.com\/blog\/powershell\/7537\/solving-the-powershell-object-age-challenge-part-1\/","url_meta":{"origin":7543,"position":3},"title":"Solving the PowerShell Object Age Challenge &#8211; Part 1","author":"Jeffery Hicks","date":"June 9, 2020","format":false,"excerpt":"A few weeks ago, the Iron Scripter site posted an interesting challenge about writing a generic function to get the age of objects. Many things that we deal with in PowerShell have an \"age\" such as files, processes or even AD groups and users. I think this is an especially\u2026","rel":"","context":"In &quot;PowerShell&quot;","block_context":{"text":"PowerShell","link":"https:\/\/jdhitsolutions.com\/blog\/category\/powershell\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage-process.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage-process.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage-process.png?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage-process.png?resize=700%2C400&ssl=1 2x, https:\/\/i0.wp.com\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage-process.png?resize=1050%2C600&ssl=1 3x"},"classes":[]},{"id":9018,"url":"https:\/\/jdhitsolutions.com\/blog\/powershell\/9018\/an-iron-scripter-warm-up-solution\/","url_meta":{"origin":7543,"position":4},"title":"An Iron Scripter Warm-Up Solution","author":"Jeffery Hicks","date":"May 6, 2022","format":false,"excerpt":"We just wrapped up the 2022 edition of the PowerShell+DevOps Global Summit. It was terrific to be with passionate PowerShell professionals again. The culmination of the event is the Iron Scripter Challenge. You can learn more about this year's event and winner here. But there is more to the Iron\u2026","rel":"","context":"In &quot;PowerShell&quot;","block_context":{"text":"PowerShell","link":"https:\/\/jdhitsolutions.com\/blog\/category\/powershell\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":6612,"url":"https:\/\/jdhitsolutions.com\/blog\/powershell\/6612\/more-fun-with-docker-containers-and-powershell\/","url_meta":{"origin":7543,"position":5},"title":"More Fun with Docker Containers and PowerShell","author":"Jeffery Hicks","date":"March 29, 2019","format":false,"excerpt":"A few days ago I shared some experiences of working with Docker containers and PowerShell. As I continue to learn Docker, I am also learning how to manage it with PowerShell. The Docker command line tools are fine but I think they are even better when drizzled with a nice\u2026","rel":"","context":"In &quot;Docker&quot;","block_context":{"text":"Docker","link":"https:\/\/jdhitsolutions.com\/blog\/category\/docker\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2019\/03\/image_thumb-20.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2019\/03\/image_thumb-20.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2019\/03\/image_thumb-20.png?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2019\/03\/image_thumb-20.png?resize=700%2C400&ssl=1 2x"},"classes":[]}],"_links":{"self":[{"href":"https:\/\/jdhitsolutions.com\/blog\/wp-json\/wp\/v2\/posts\/7543","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/jdhitsolutions.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/jdhitsolutions.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/jdhitsolutions.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/jdhitsolutions.com\/blog\/wp-json\/wp\/v2\/comments?post=7543"}],"version-history":[{"count":0,"href":"https:\/\/jdhitsolutions.com\/blog\/wp-json\/wp\/v2\/posts\/7543\/revisions"}],"wp:attachment":[{"href":"https:\/\/jdhitsolutions.com\/blog\/wp-json\/wp\/v2\/media?parent=7543"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/jdhitsolutions.com\/blog\/wp-json\/wp\/v2\/categories?post=7543"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/jdhitsolutions.com\/blog\/wp-json\/wp\/v2\/tags?post=7543"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}