{"id":7537,"date":"2020-06-09T11:19:32","date_gmt":"2020-06-09T15:19:32","guid":{"rendered":"https:\/\/jdhitsolutions.com\/blog\/?p=7537"},"modified":"2020-06-09T11:19:32","modified_gmt":"2020-06-09T15:19:32","slug":"solving-the-powershell-object-age-challenge-part-1","status":"publish","type":"post","link":"https:\/\/jdhitsolutions.com\/blog\/powershell\/7537\/solving-the-powershell-object-age-challenge-part-1\/","title":{"rendered":"Solving the PowerShell Object Age Challenge &#8211; Part 1"},"content":{"rendered":"<p>A few weeks ago, the Iron Scripter site posted an interesting challenge about <a href=\"https:\/\/ironscripter.us\/a-powershell-object-age-challenge\/\" target=\"_blank\" rel=\"noopener noreferrer\">writing a generic function to get the age of objects<\/a>. 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 useful exercise because it forces you to understand the PowerShell paradigm of \"objects in the pipeline.\"<\/p>\n<h2>Methodology<\/h2>\n<p>I always find it helpful to try and visualize the pipeline process. If nothing else, you need to be able to verbally describe what your command is going to do. If you can't, you will have a very hard time writing code. I knew I would have to identify at least one property for the object to use when retrieving the creation time. Because part of the challenge was to get an average age of all processed objects, I knew I had to do the bulk of the work in the End script block. Here's my verbalization:<\/p>\n<blockquote><p>Initialize an array to hold incoming objects. As objects are piped in to the function add each function to the temporary array. Once everything has been processed, then calculate the average age. For each object in the temporary array create a custom result with calculated time spans.<\/p><\/blockquote>\n<p>That's a decent starting point.<\/p>\n<h2>Outlining the Code<\/h2>\n<p>Sketching this out in code I come up with this.<\/p>\n<pre class=\"lang:ps mark:0 decode:true\">Begin {\n  #initialize holding array\n}\nProcess {\n    foreach ($item in $input) {\n    #add each item to the array\n    }\n}\nEnd {\n    #calculate average age for all items in holding array\n    $Age = #code\n    foreach ($object in $array) {\n      #create custom object \n      [pscustomobject]@{\n        Created  = $object.$CreationProperty\n        Modified = $object.$ChangeProperty\n        Age      = $Age\n        Average  = If ($Age -gt $Avg) {\"Above\"} else {\"Below\"}\n      }\n    }\n}\n<\/pre>\n<p>I expect this to change but it gives me a good place to start. For parameters, I can take input for any type of object. But I'll have to rely on the user to know what to use for the creation property and optionally a property that indicates when the object was last changed. It is possible an object may not have a \"changed\" property , so I'll use the creation property by default. I also want the user to be able to specify additional parameters they'd like in the output.<\/p>\n<pre class=\"lang:ps mark:0 decode:true\">Param(\n        [Parameter(Mandatory, ValueFromPipeline)]\n        [ValidateNotNullorEmpty()]\n        [object[]]$InputObject,\n\n        [Parameter(Mandatory, Position = 0)]\n        [string]$CreationProperty,\n\n        [Parameter(Position = 1)]\n        [string]$ChangeProperty,\n\n        [object[]]$Properties = @(\"Name\")\n    )\n<\/pre>\n<p>This seems manageable.<\/p>\n<p>Normally, I'd simply initalize an empty array. But to make things more interesting I'm going to use a generic list object.<\/p>\n<pre class=\"lang:ps mark:0 decode:true\">$all = [System.Collections.Generic.List[object]]::new()\n<\/pre>\n<p>This type of object is supposed to be better performing. As a quick test, this code<\/p>\n<pre class=\"lang:ps mark:0 decode:true\">measure-command {\n $all = [System.Collections.Generic.List[object]]::new()\n dir c:\\scripts -file -Recurse | foreach-object { $all.Add($_) }\n}\n<\/pre>\n<p>took 2.95 seconds to add almost 7900 files. Compared to the \"traditional\" approach.<\/p>\n<pre class=\"lang:ps mark:0 decode:true\">measure-command {\n $all = @()\n dir c:\\scripts -file -Recurse | foreach-object { $all+=$_ }\n}\n<\/pre>\n<p>Which took 5.5 seconds. I don't want to get sidetracked into a discussion of the merits of either approach, but for this solution I'll use the generic list. I'm basically going to use the same pattern: for each piped in object, add it to the list.<\/p>\n<p>In the End block I can now process all objects. First, I need to build an array of the creation age timespan for each object.<\/p>\n<pre class=\"lang:ps mark:0 decode:true\">$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<\/pre>\n<p>Then I can measure that collection and generate an average timespan. I'll use TotalSeconds.<\/p>\n<pre class=\"lang:ps mark:0 decode:true\">$avg = New-TimeSpan -seconds ($allTimeSpans | Measure-Object -Property TotalSeconds -average).Average\n<\/pre>\n<p>I'll use this value as a comparison for the creation age of each object so I can indicate if it is above or below average.<\/p>\n<p>For each file in the list, I'll create an [ordered] hashtable. I did this because I wanted at least some structure to the output.<\/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       Created  = $item.$CreationProperty\n       Modified = $item.$ChangeProperty\n       Age      = $Age\n       Average  = If ($Age -gt $Avg) {\"Above\"} else {\"Below\"}\n    }\n<\/pre>\n<p>Next, I need to account for the extra properties. I also decided to support a hashtable like you would use with Select-Object.<\/p>\n<pre class=\"lang:ps mark:0 decode:true\">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    else {\n        $val = $item.$prop\n    }\n    #shorten the value for the verbose message\n    if ($VerbosePreference -eq \"Continue\") {\n        $valtest = ($val | Out-String).trim()\n        if ($valtest.length -ge 19) {\n            $valstring = \"{0}..\" -f $valtest.Substring(0, 19)\n        }\n        else {\n            $valstring = $valtest\n        }\n    }\n    Write-Verbose \"[END    ] Adding Property: $prop Value: $valstring\"\n    $tmpHash.Add($prop, $val)\n} #foreach property\n<\/pre>\n<p>The gist of this to get the property value for each object and add it as a new element in the temporary hashtable.\u00a0I\u00a0 alsolike using Verbose messages and in my testing I decided to shorten up the value text to keep the verbose messages from wrapping. Purely cosmetic.<\/p>\n<p>The last step for each object is to create the custom object.<\/p>\n<pre class=\"lang:ps mark:0 decode:true\">New-Object -TypeName PSObject -property $tmpHash\n<\/pre>\n<h2>Extras<\/h2>\n<p>I decided to have a little extra fun with this function. First, after the results are displayed, I use Write-Host to display a colorized summary of the average overall creation age.<\/p>\n<pre class=\"lang:ps mark:0 decode:true\">Write-Host \"`n$([char]0x1b)[1;38;5;154mAverage Creation Age Overall : $avg\"\n<\/pre>\n<p>I'm even using an ANSI escape sequence to color the output with a custom color. Why Write-Host? Functions should only write one type of object to the pipeline, and mine already is. I didn't want it to also write a string. I could have added this information as a property to each object, but instead decided to simply write information to the console and not the pipeline. This escape sequence will work in Windows PowerShell and PwoerShell 7.<\/p>\n<p>The other thing I wanted to try was a console-style progress indicator. I wanted to have something to indicate items were being processed and added to the list. Again, a little ANSI magic to the rescue.<\/p>\n<p>In the Begin block, I define an array of characters and a counter.<\/p>\n<pre class=\"lang:ps mark:0 decode:true\">$ch = @(\"|\", \"\/\", \"-\", \"\\\", \"|\", \"\/\", \"-\")\n$c = 0\n<\/pre>\n<p>As each item is processed, I use Write-Host with an ANSI sequence that makes sure each line is written at the same position.<\/p>\n<pre class=\"lang:ps mark:0 decode:true\">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    #adding an artificial sleep so the progress wheel looks like it is spinning\n    Start-Sleep -Milliseconds 10\n}\n<\/pre>\n<p>The end result is a little spinner. I added an artificial sleep to make it look nicer. And I only use the progress spinner when NOT using -Verbose since the list of Verbose messages would move the spinner off the screen pretty quickly.<\/p>\n<h2>The Complete Function<\/h2>\n<p>Here's the complete function. I will not display the ANSI sequences very well in the PowerShell ISE.<\/p>\n<pre class=\"lang:ps mark:0 decode:true\">Function Get-ObjectAge {\n    [cmdletbinding()]\n    [alias('goa')]\n\n    Param(\n        [Parameter(Mandatory, ValueFromPipeline)]\n        [ValidateNotNullorEmpty()]\n        [object[]]$InputObject,\n\n        [Parameter(Mandatory, Position = 0)]\n        [string]$CreationProperty,\n\n        [Parameter(Position = 1)]\n        [string]$ChangeProperty,\n\n        [object[]]$Properties = @(\"Name\")\n    )\n\n    Begin {\n        Write-Verbose \"[BEGIN  ] Starting: $($MyInvocation.Mycommand)\"\n\n        #create a list object to hold all incoming objects.\n        #this should perform slightly better than a traditional array\n        $all = [System.Collections.Generic.List[object]]::new()\n\n        #use the same date time for all timespan calculations\n        $Now = Get-Date\n        Write-Verbose \"[BEGIN  ] Using $NOW for timespan calculations\"\n        Write-Verbose \"[BEGIN  ] Using $CreationProperty for CreationProperty\"\n\n        #use $CreationProperty parameter value for Change 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 wheel 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                #adding an artificial sleep so the progress wheel looks like it is spinning\n                Start-Sleep -Milliseconds 10\n            }\n            #add the incoming object to the list\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        #get the average creation age timespan\n        $avg = New-TimeSpan -seconds ($allTimeSpans | Measure-Object -Property TotalSeconds -average).Average\n\n        #create a result object for each processed object and save to an array\n        $results = foreach ($item in $all) {\n            $Age = New-TimeSpan -end $Now -Start ($item.$creationProperty)\n            $tmpHash = [ordered]@{\n                Created  = $item.$CreationProperty\n                Modified = $item.$ChangeProperty\n                Age      = $Age\n                Average  = If ($Age -gt $Avg) {\"Above\"} else {\"Below\"}\n            }\n\n            #add user specified properties\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                else {\n                    $val = $item.$prop\n                }\n                #shorten the value for the verbose message\n                if ($VerbosePreference -eq \"Continue\") {\n                    $valtest = ($val | Out-String).trim()\n                    if ($valtest.length -ge 19) {\n                        $valstring = \"{0}..\" -f $valtest.Substring(0, 19)\n                    }\n                    else {\n                        $valstring = $valtest\n                    }\n                }\n                Write-Verbose \"[END    ] Adding Property: $prop Value: $valstring\"\n                $tmpHash.Add($prop, $val)\n            } #foreach property\n\n            #create the object\n            New-Object -TypeName PSObject -property $tmpHash\n\n        } #foreach item\n\n        #display all the results at once\n        $results\n\n        #display a message about the average creation age\n        Write-Host \"`n$([char]0x1b)[1;38;5;154mAverage Creation Age Overall : $avg\"\n        \n        Write-Verbose \"[END    ] Ending: $($MyInvocation.Mycommand)\"\n    } #end\n} #end function\n<\/pre>\n<p>Want to see it work? The function works with files.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-7540\" src=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage-file.png\" alt=\"get-objectage-file\" width=\"1064\" height=\"534\" srcset=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage-file.png 1064w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage-file-300x151.png 300w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage-file-1024x514.png 1024w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage-file-768x385.png 768w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage-file-850x427.png 850w\" sizes=\"auto, (max-width: 1064px) 100vw, 1064px\" \/>It also works with processes. This example is using the alias I defined for the function.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-7541\" src=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage-process.png\" alt=\"get-objectage-process\" width=\"1064\" height=\"534\" srcset=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage-process.png 1064w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage-process-300x151.png 300w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage-process-1024x514.png 1024w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage-process-768x385.png 768w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage-process-850x427.png 850w\" sizes=\"auto, (max-width: 1064px) 100vw, 1064px\" \/><\/p>\n<h2>Making It Better<\/h2>\n<p>I have a generic enough function that works with any object type, assuming it has a property that reflects when it was created. And as nice as this is I know it can be even better. But I think I've given you enough to play with for one day so I'll save my enhanced version for another day...soon.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>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 &#8220;age&#8221; such as files, processes or even AD groups and users. I think this is an especially useful exercise because it forces&#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":"New on the blog: Solving the #PowerShell Object Age Challenge - Part 1","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,630,534,540],"class_list":["post-7537","post","type-post","status-publish","format-standard","hentry","category-powershell","category-scripting","tag-ansi","tag-function","tag-iron-scripter","tag-list","tag-powershell","tag-scripting"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.4 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Solving the PowerShell Object Age Challenge - Part 1 &#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\/7537\/solving-the-powershell-object-age-challenge-part-1\/\" \/>\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 1 &#8226; The Lonely Administrator\" \/>\n<meta property=\"og:description\" content=\"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 &quot;age&quot; such as files, processes or even AD groups and users. I think this is an especially useful exercise because it forces...\" \/>\n<meta property=\"og:url\" content=\"https:\/\/jdhitsolutions.com\/blog\/powershell\/7537\/solving-the-powershell-object-age-challenge-part-1\/\" \/>\n<meta property=\"og:site_name\" content=\"The Lonely Administrator\" \/>\n<meta property=\"article:published_time\" content=\"2020-06-09T15:19:32+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage-file.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=\"8 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/powershell\\\/7537\\\/solving-the-powershell-object-age-challenge-part-1\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/powershell\\\/7537\\\/solving-the-powershell-object-age-challenge-part-1\\\/\"},\"author\":{\"name\":\"Jeffery Hicks\",\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/#\\\/schema\\\/person\\\/d0258030b41f07fd745f4078bdf5b6c9\"},\"headline\":\"Solving the PowerShell Object Age Challenge &#8211; Part 1\",\"datePublished\":\"2020-06-09T15:19:32+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/powershell\\\/7537\\\/solving-the-powershell-object-age-challenge-part-1\\\/\"},\"wordCount\":945,\"commentCount\":1,\"publisher\":{\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/#\\\/schema\\\/person\\\/d0258030b41f07fd745f4078bdf5b6c9\"},\"image\":{\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/powershell\\\/7537\\\/solving-the-powershell-object-age-challenge-part-1\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/wp-content\\\/uploads\\\/2020\\\/06\\\/get-objectage-file.png\",\"keywords\":[\"ANSI\",\"Function\",\"Iron Scripter\",\"List\",\"PowerShell\",\"Scripting\"],\"articleSection\":[\"PowerShell\",\"Scripting\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/powershell\\\/7537\\\/solving-the-powershell-object-age-challenge-part-1\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/powershell\\\/7537\\\/solving-the-powershell-object-age-challenge-part-1\\\/\",\"url\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/powershell\\\/7537\\\/solving-the-powershell-object-age-challenge-part-1\\\/\",\"name\":\"Solving the PowerShell Object Age Challenge - Part 1 &#8226; The Lonely Administrator\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/powershell\\\/7537\\\/solving-the-powershell-object-age-challenge-part-1\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/powershell\\\/7537\\\/solving-the-powershell-object-age-challenge-part-1\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/wp-content\\\/uploads\\\/2020\\\/06\\\/get-objectage-file.png\",\"datePublished\":\"2020-06-09T15:19:32+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/powershell\\\/7537\\\/solving-the-powershell-object-age-challenge-part-1\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/powershell\\\/7537\\\/solving-the-powershell-object-age-challenge-part-1\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/powershell\\\/7537\\\/solving-the-powershell-object-age-challenge-part-1\\\/#primaryimage\",\"url\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/wp-content\\\/uploads\\\/2020\\\/06\\\/get-objectage-file.png\",\"contentUrl\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/wp-content\\\/uploads\\\/2020\\\/06\\\/get-objectage-file.png\",\"width\":1064,\"height\":534},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/powershell\\\/7537\\\/solving-the-powershell-object-age-challenge-part-1\\\/#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 1\"}]},{\"@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 1 &#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\/7537\/solving-the-powershell-object-age-challenge-part-1\/","og_locale":"en_US","og_type":"article","og_title":"Solving the PowerShell Object Age Challenge - Part 1 &#8226; The Lonely Administrator","og_description":"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 useful exercise because it forces...","og_url":"https:\/\/jdhitsolutions.com\/blog\/powershell\/7537\/solving-the-powershell-object-age-challenge-part-1\/","og_site_name":"The Lonely Administrator","article_published_time":"2020-06-09T15:19:32+00:00","og_image":[{"url":"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage-file.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":"8 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/jdhitsolutions.com\/blog\/powershell\/7537\/solving-the-powershell-object-age-challenge-part-1\/#article","isPartOf":{"@id":"https:\/\/jdhitsolutions.com\/blog\/powershell\/7537\/solving-the-powershell-object-age-challenge-part-1\/"},"author":{"name":"Jeffery Hicks","@id":"https:\/\/jdhitsolutions.com\/blog\/#\/schema\/person\/d0258030b41f07fd745f4078bdf5b6c9"},"headline":"Solving the PowerShell Object Age Challenge &#8211; Part 1","datePublished":"2020-06-09T15:19:32+00:00","mainEntityOfPage":{"@id":"https:\/\/jdhitsolutions.com\/blog\/powershell\/7537\/solving-the-powershell-object-age-challenge-part-1\/"},"wordCount":945,"commentCount":1,"publisher":{"@id":"https:\/\/jdhitsolutions.com\/blog\/#\/schema\/person\/d0258030b41f07fd745f4078bdf5b6c9"},"image":{"@id":"https:\/\/jdhitsolutions.com\/blog\/powershell\/7537\/solving-the-powershell-object-age-challenge-part-1\/#primaryimage"},"thumbnailUrl":"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage-file.png","keywords":["ANSI","Function","Iron Scripter","List","PowerShell","Scripting"],"articleSection":["PowerShell","Scripting"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/jdhitsolutions.com\/blog\/powershell\/7537\/solving-the-powershell-object-age-challenge-part-1\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/jdhitsolutions.com\/blog\/powershell\/7537\/solving-the-powershell-object-age-challenge-part-1\/","url":"https:\/\/jdhitsolutions.com\/blog\/powershell\/7537\/solving-the-powershell-object-age-challenge-part-1\/","name":"Solving the PowerShell Object Age Challenge - Part 1 &#8226; The Lonely Administrator","isPartOf":{"@id":"https:\/\/jdhitsolutions.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/jdhitsolutions.com\/blog\/powershell\/7537\/solving-the-powershell-object-age-challenge-part-1\/#primaryimage"},"image":{"@id":"https:\/\/jdhitsolutions.com\/blog\/powershell\/7537\/solving-the-powershell-object-age-challenge-part-1\/#primaryimage"},"thumbnailUrl":"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage-file.png","datePublished":"2020-06-09T15:19:32+00:00","breadcrumb":{"@id":"https:\/\/jdhitsolutions.com\/blog\/powershell\/7537\/solving-the-powershell-object-age-challenge-part-1\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/jdhitsolutions.com\/blog\/powershell\/7537\/solving-the-powershell-object-age-challenge-part-1\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/jdhitsolutions.com\/blog\/powershell\/7537\/solving-the-powershell-object-age-challenge-part-1\/#primaryimage","url":"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage-file.png","contentUrl":"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-objectage-file.png","width":1064,"height":534},{"@type":"BreadcrumbList","@id":"https:\/\/jdhitsolutions.com\/blog\/powershell\/7537\/solving-the-powershell-object-age-challenge-part-1\/#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 1"}]},{"@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":9018,"url":"https:\/\/jdhitsolutions.com\/blog\/powershell\/9018\/an-iron-scripter-warm-up-solution\/","url_meta":{"origin":7537,"position":0},"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":7489,"url":"https:\/\/jdhitsolutions.com\/blog\/powershell\/7489\/powershell-word-play\/","url_meta":{"origin":7537,"position":1},"title":"PowerShell Word Play","author":"Jeffery Hicks","date":"May 19, 2020","format":false,"excerpt":"A few weeks ago an Iron Scripter PowerShell challenge was issued that involved playing with words and characters. Remember, the Iron Scripter challenges aren't intended to create meaningful, production worthy code. They are designed to help you learn PowerShell fundamentals and scripting techniques. This particular challenge was aimed at beginner\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\/05\/doublechar.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/05\/doublechar.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/05\/doublechar.png?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/05\/doublechar.png?resize=700%2C400&ssl=1 2x, https:\/\/i0.wp.com\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/05\/doublechar.png?resize=1050%2C600&ssl=1 3x, https:\/\/i0.wp.com\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/05\/doublechar.png?resize=1400%2C800&ssl=1 4x"},"classes":[]},{"id":8107,"url":"https:\/\/jdhitsolutions.com\/blog\/powershell\/8107\/scripting-challenge-meetup\/","url_meta":{"origin":7537,"position":2},"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":8236,"url":"https:\/\/jdhitsolutions.com\/blog\/powershell\/8236\/solving-another-powershell-math-challenge\/","url_meta":{"origin":7537,"position":3},"title":"Solving Another PowerShell Math Challenge","author":"Jeffery Hicks","date":"March 22, 2021","format":false,"excerpt":"Last month, the Iron Scripter Chairman posted a \"fun\" PowerShell scripting challenge. Actually, a few math-related challenges . As with all these challenges, the techniques and concepts you use to solve the challenge are more important than the result itself. Here's how I approached the problems. Problem #1 The first\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\/03\/get-possiblesum.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/03\/get-possiblesum.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/03\/get-possiblesum.png?resize=525%2C300&ssl=1 1.5x"},"classes":[]},{"id":7024,"url":"https:\/\/jdhitsolutions.com\/blog\/powershell\/7024\/managing-the-recycle-bin-with-powershell\/","url_meta":{"origin":7537,"position":4},"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":7559,"url":"https:\/\/jdhitsolutions.com\/blog\/powershell\/7559\/an-expanded-powershell-scripting-inventory-tool\/","url_meta":{"origin":7537,"position":5},"title":"An Expanded PowerShell Scripting Inventory Tool","author":"Jeffery Hicks","date":"June 19, 2020","format":false,"excerpt":"The other day I shared my code that I worked up to solve an Iron Scripter PowerShell challenge. One of the shortcomings was that I didn't address a challenge to include a property that would indicate what file was using a given command. I also felt I could do better\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-psscriptinventory.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-psscriptinventory.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-psscriptinventory.png?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-psscriptinventory.png?resize=700%2C400&ssl=1 2x, https:\/\/i0.wp.com\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-psscriptinventory.png?resize=1050%2C600&ssl=1 3x, https:\/\/i0.wp.com\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2020\/06\/get-psscriptinventory.png?resize=1400%2C800&ssl=1 4x"},"classes":[]}],"_links":{"self":[{"href":"https:\/\/jdhitsolutions.com\/blog\/wp-json\/wp\/v2\/posts\/7537","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=7537"}],"version-history":[{"count":0,"href":"https:\/\/jdhitsolutions.com\/blog\/wp-json\/wp\/v2\/posts\/7537\/revisions"}],"wp:attachment":[{"href":"https:\/\/jdhitsolutions.com\/blog\/wp-json\/wp\/v2\/media?parent=7537"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/jdhitsolutions.com\/blog\/wp-json\/wp\/v2\/categories?post=7537"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/jdhitsolutions.com\/blog\/wp-json\/wp\/v2\/tags?post=7537"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}