{"id":3432,"date":"2013-06-09T00:01:00","date_gmt":"2013-06-09T00:01:00","guid":{"rendered":"https:\/\/blogs.technet.microsoft.com\/heyscriptingguy\/2013\/06\/09\/weekend-scripter-use-powershell-for-conditional-user-profile-removal\/"},"modified":"2013-06-09T00:01:00","modified_gmt":"2013-06-09T00:01:00","slug":"weekend-scripter-use-powershell-for-conditional-user-profile-removal","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/scripting\/weekend-scripter-use-powershell-for-conditional-user-profile-removal\/","title":{"rendered":"Weekend Scripter: Use PowerShell for Conditional User Profile Removal"},"content":{"rendered":"<p><strong style=\"font-size: 12px\">Summary<\/strong><span style=\"font-size: 12px\">: Guest blogger, Bob Stevens, talks about how to use Windows PowerShell to perform conditional user profile removal.<\/span><\/p>\n<p>Microsoft Scripting Guy, Ed Wilson, is here. Today we are lucky to have guest blogger, Bob Stevens, return for another exciting and useful blog post. Be sure you check out <a href=\"http:\/\/blogs.technet.com\/b\/heyscriptingguy\/archive\/tags\/bob+stevens\/\" target=\"_blank\">Bob&#8217;s other Hey, Scripting Guy! Blog posts<\/a>. Bob is a member of the Twin Cities PowerShell User Group. Here is his contact information:<\/p>\n<p style=\"padding-left: 30px\">Blog: <a href=\"http:\/\/stuckinmypowershell.blogspot.com\/\" target=\"_blank\">Help! I&rsquo;m Stuck in My PowerShell!<\/a><br \/> Twitter: <a href=\"https:\/\/twitter.com\/B_stevens6\" target=\"_blank\">@B_stevens6<\/a><br \/> LinkedIn: <a href=\"http:\/\/www.linkedin.com\/profile\/edit?trk=hb_tab_pro_top\" target=\"_blank\">Robert Stevens<\/a><\/p>\n<p style=\"padding-left: 30px\"><strong>Note<\/strong>&nbsp;&nbsp;&nbsp;This is the second post in a two part series about using Windows PowerShell to work with user profiles. You should read yesterday&rsquo;s post, <a href=\"http:\/\/blogs.technet.com\/b\/heyscriptingguy\/archive\/2013\/06\/08\/weekend-scripter-use-powershell-to-generate-a-recent-profile-report.aspx\">Use PowerShell to Generate a Recent Profile Report<\/a>, prior to reading today&rsquo;s.<\/p>\n<p>And now, here&rsquo;s Bob&hellip;<\/p>\n<p>In my last post I discussed how to use Windows PowerShell to return a list of profiles that have not been accessed in the last 30 days by using the metadata of the ntuser.dat file:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/3223.6-9-13-1.png\"><img decoding=\"async\" title=\"Image of command output\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/3223.6-9-13-1.png\" alt=\"Image of command output\" \/><\/a><\/p>\n<p>In response to input from Ed Wilson, I decided to modify this script heavily to not only return the opposite (profiles that have not been accessed in the past 30 days), but also to provide the user the option of removing such profiles. Although this seems rather straight forward, a number of issues do exist. Primary among them is the built-in profiles, such as Administrator. As you may well know, removing the local Administrator profile can cause some issues, especially if the Group Policy Objects (GPOs) for your organization alter this profile.<\/p>\n<p>Before we address that, we need to alter the script from my previous blog post. In that post, we used the ntuser.dat file to define the last time the profile was used. This is not going to be necessary now because we are looking for the last time the profile has been used at all, not the last time the user&rsquo;s registry hive was loaded. Because of this, we can confidantly reduce the complexity of the first part of our script by removing the wildcard character.<\/p>\n<p style=\"padding-left: 30px\">Get-Childitem -force &#8220;C:\\Documents and Settings&#8221;<\/p>\n<p>Here you see the <strong>Get-Childitem<\/strong> cmdlet followed by the <strong>-force<\/strong> switch. (This is followed by the profile path for the version 5 Windows systems, whichI will address later in this post). This returns the content of our defined directory, including hidden and system objects, C:\\Documents and Settings or C:\\Users.<\/p>\n<p>Now we need to pipe the output from that command to a conditional statement that is very similar to the previous script. The only alterations we are going to make are with the inequality statement and the value. These we change from less than (<strong>-le<\/strong>) to greater than (<strong>-ge<\/strong>), and we change the number of days from 31 to 30.<\/p>\n<p style=\"padding-left: 30px\">Get-ChildItem -force &#8220;C:\\Documents and Settings&#8221; | Where {<\/p>\n<p style=\"padding-left: 30px\">((Get-Date)-$_.lastwritetime).days <strong>-ge 30<\/strong><\/p>\n<p style=\"padding-left: 30px\">}<\/p>\n<p><span style=\"font-size: 12px\">You can stop here if you simply want the list of profiles that were not accessed in the last 30 days. If you want to move on, we need to alter that first line to pipe it to an array. An array is similar to a variable; however, with an array you are defining multiple values within a single variable. This is done by encapsulating the values in parentheses and preceding the left parentheses with an ampersand (@).<\/span><\/p>\n<p style=\"padding-left: 30px\">$over30dayprofiles = @(Get-ChildItem -force &#8220;C:\\Documents and Settings&#8221; | Where {<\/p>\n<p style=\"padding-left: 30px\">((Get-Date)-$_.lastwritetime).days -ge 30<\/p>\n<p style=\"padding-left: 30px\">} )<\/p>\n<p><span style=\"font-size: 12px\">We need to use a pair of IF statements to avoid having to change the script on a case-by-case basis. The structure of these commands are explained in detail in my previous post, <\/span><a href=\"http:\/\/blogs.technet.com\/b\/heyscriptingguy\/archive\/2013\/06\/08\/weekend-scripter-use-powershell-to-generate-a-recent-profile-report.aspx\" target=\"_blank\">Use PowerShell to Generate a Recent Profile Report<\/a><span style=\"font-size: 12px\">, so feel free to backtrack if you want to furthur understand them.<\/span><\/p>\n<p style=\"padding-left: 30px\"><span style=\"font-size: 12px\">IF ((Get-WmiObject Win32_OperatingSystem).version &ndash;like &ldquo;5*&rdquo;) {}<\/span><\/p>\n<p style=\"padding-left: 30px\">IF ((Get-WmiObject Win32_OperatingSystem).version &ndash;like &ldquo;6*&rdquo;) {}<\/p>\n<p><span style=\"font-size: 12px\">Inside the braces that follow the first IF statement&rsquo;s condition, we need to nest our variable declaration<\/span><strong style=\"font-size: 12px\"> $over30dayprofiles<\/strong><span style=\"font-size: 12px\">.<\/span><\/p>\n<p style=\"padding-left: 30px\"><span style=\"font-size: 12px\">IF ((Get-WmiObject Win32_OperatingSystem).version &ndash;like &ldquo;5*&rdquo;) {<\/span><\/p>\n<p style=\"padding-left: 30px\">$over30dayprofiles = @(Get-ChildItem -force &#8220;C:\\Documents and Settings&#8221; | Where {<\/p>\n<p style=\"padding-left: 30px\">((Get-Date)-$_.lastwritetime).days -ge 30<\/p>\n<p style=\"padding-left: 30px\">} )<\/p>\n<p style=\"padding-left: 30px\">}<\/p>\n<p><span style=\"font-size: 12px\">We now nest the same variable in the second IF statement&rsquo;s declaration with one minor variation. We replace &ldquo;Documents and Settings&rdquo; with &ldquo;Users.&rdquo;<\/span><\/p>\n<p style=\"padding-left: 30px\"><span style=\"font-size: 12px\">IF ((Get-WmiObject Win32_OperatingSystem).version &ndash;like &ldquo;6*&rdquo;) {<\/span><\/p>\n<p style=\"padding-left: 30px\">$over30dayprofiles = @(Get-ChildItem -force &#8220;C:\\Users&#8221; | Where {<\/p>\n<p style=\"padding-left: 30px\">((Get-Date)-$_.lastwritetime).days -ge 30<\/p>\n<p style=\"padding-left: 30px\">} )<\/p>\n<p style=\"padding-left: 30px\">}<\/p>\n<p><span style=\"font-size: 12px\">Roll them together to have a complete picture of our array declaration:<\/span><\/p>\n<p style=\"padding-left: 30px\"><span style=\"font-size: 12px\">IF ((Get-WmiObject Win32_OperatingSystem).version &ndash;like &ldquo;5*&rdquo;) {<\/span><\/p>\n<p style=\"padding-left: 30px\">$over30dayprofiles = @(Get-ChildItem -force &#8220;C:\\Documents and Settings&#8221; | Where {<\/p>\n<p style=\"padding-left: 30px\">((Get-Date)-$_.lastwritetime).days -ge 30<\/p>\n<p style=\"padding-left: 30px\">} )<\/p>\n<p style=\"padding-left: 30px\">}<\/p>\n<p style=\"padding-left: 30px\">IF ((Get-WmiObject Win32_OperatingSystem).version &ndash;like &ldquo;6*&rdquo;) {<\/p>\n<p style=\"padding-left: 30px\">$over30dayprofiles = @(Get-ChildItem -force &#8220;C:\\Users&#8221; | Where {<\/p>\n<p style=\"padding-left: 30px\">((Get-Date)-$_.lastwritetime).days -ge 30<\/p>\n<p style=\"padding-left: 30px\">} )<\/p>\n<p style=\"padding-left: 30px\">}<\/p>\n<p><span style=\"font-size: 12px\">Combined, our pair of IF statements declare our variable, <\/span><strong style=\"font-size: 12px\">$over30dayprofiles<\/strong><span style=\"font-size: 12px\">, as all items with a last write time of greater than 30 days. To manipulate this data into the correct format, we use the <\/span><strong style=\"font-size: 12px\">Set-Content<\/strong><span style=\"font-size: 12px\"> cmdlet. <\/span><strong style=\"font-size: 12px\">Set-content<\/strong><span style=\"font-size: 12px\"> will replace the context of a text file, and it natively does so without all of the metadata, which is exactly what we need. (If you want to append data to the text file, use <\/span><strong style=\"font-size: 12px\">Add-Content<\/strong><span style=\"font-size: 12px\">.) With that, we have the following command:<\/span><\/p>\n<p style=\"padding-left: 30px\"><span style=\"font-size: 12px\">Set-Content &#8220;.\\over30dayprofiles.txt&#8221; $over30dayprofiles<\/span><\/p>\n<p>Here is the result:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/5305.6-9-13-2.png\"><img decoding=\"async\" title=\"Image of command output\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/5305.6-9-13-2.png\" alt=\"Image of command output\" \/><\/a><\/p>\n<p>Notice how Administrator, LocalService and NetworkService are present. We need those to remain, so we are going to actually remove them, and set the content to another file. To do this, we first need to get the content of this file with the <strong>Get-Content<\/strong> cmdlet:<\/p>\n<p style=\"padding-left: 30px\">Get-Content &#8220;.\\over30dayprofiles.txt&#8221;<\/p>\n<p>Now we use the pipe operator (<strong>|<\/strong>) to funnel the content of <strong>over30dayprofiles.txt<\/strong> &nbsp;into the <strong>ForEach-Object<\/strong> cmdlet.<\/p>\n<p style=\"padding-left: 30px\">Get-Content &#8220;.\\over30dayprofiles.txt&#8221; | ForEach-Object<\/p>\n<p><strong>ForEach-Object<\/strong> is simply stating, &ldquo;For each object, do this.&rdquo; The &ldquo;do this&rdquo; must be encapsulated in braces because it is followed by actual script.<\/p>\n<p style=\"padding-left: 30px\">Get-Content &#8220;.\\over30dayprofiles.txt&#8221; | ForEach-Object{}<\/p>\n<p>Inside the braces, we replace Administrator, LocalService, and NetworkService (with any other profiles you want to exempt) with &nbsp;a null value by using two single quotation marks with nothing between them (<strong>&lsquo;&rsquo;<\/strong>). This is done with the <strong>-replace<\/strong> operator.<\/p>\n<p style=\"padding-left: 30px\"><strong>Note<\/strong>&nbsp;&nbsp;&nbsp;Remember that you need to have the placeholder,&nbsp;<strong>$_,<\/strong> on the first line because it is necessary for the operation of <strong>ForEach-Object<\/strong>. The placeholder, <strong>$_<\/strong>, simply tells Windows PowerShell to address each individual object within the data provided.<\/p>\n<p style=\"padding-left: 30px\">$_ -replace &#8216;Administrator&#8217;, &#8221; `<br \/> &nbsp; -replace &#8216;LocalService&#8217;, &#8221; `<br \/> &nbsp; -replace &#8216;NetworkService&#8217;, &#8221; `<\/p>\n<p><span style=\"font-size: 12px\">It is important to clarify the structure of the <\/span><strong style=\"font-size: 12px\">-replace<\/strong><span style=\"font-size: 12px\"> operator. <\/span><strong style=\"font-size: 12px\">-Replace<\/strong><span style=\"font-size: 12px\"> must be followed by the item to be replaced, which is encapsulated in single quotation marks(<\/span><strong style=\"font-size: 12px\">&lsquo;<\/strong><span style=\"font-size: 12px\">) and followed by a comma (<\/span><strong style=\"font-size: 12px\">,<\/strong><span style=\"font-size: 12px\">). The comma is followed by what is being replaced, and it is also encapsulated in single quotation marks.<\/span><\/p>\n<p style=\"padding-left: 30px\"><strong>Note<\/strong>&nbsp;&nbsp;&nbsp;Because we are replacing these items with nothing, the single quotation marks look like double quotation marks. Be careful to make this distinction because this code requires single quotation marks.<\/p>\n<p>Finally, we use the accent (<strong>`<\/strong>) character to signify &ldquo;next line.&rdquo; As you can see, only the first line requires the placeholder.<\/p>\n<p>So far, this set of script looks like this:<\/p>\n<p style=\"padding-left: 30px\">Get-Content &#8220;.\\over30dayprofiles.txt&#8221; | ForEach-Object {<\/p>\n<p style=\"padding-left: 30px\">$_ -replace &#8216;Administrator&#8217;, &#8221; `<br \/> &nbsp; -replace &#8216;LocalService&#8217;, &#8221; `<br \/> &nbsp; -replace &#8216;NetworkService&#8217;, &#8221; `<\/p>\n<p style=\"padding-left: 30px\">&nbsp; }<\/p>\n<p><span style=\"font-size: 12px\">Now that we have removed those important profiles, we need to remove any empty lines (also known as whitespace) that may be in our file. To do this, we do not look for actual white space because that is tantamount to looking for nothing. Rather, we look for lines that have an alpha character in them. This is accomplished by piping the results of the previous code to the <\/span><strong style=\"font-size: 12px\">Select-String<\/strong><span style=\"font-size: 12px\"> cmdlet and a regular expression. We start with the pipe operator and the <\/span><strong style=\"font-size: 12px\">Select String<\/strong><span style=\"font-size: 12px\"> cmdlet:<\/span><\/p>\n<p style=\"padding-left: 30px\">| Select-String<\/p>\n<p><strong>Select-String<\/strong> requires two things to operate properly: the path and the pattern. Thankfully, the path (location of the data) is taken care of by the pipe operator. We add the <strong>-pattern<\/strong> definition followed by the <strong>Word<\/strong> regular expression (<strong>\\w<\/strong>). The <strong>Word<\/strong> regular expression defines an alphabetical character. The following script states, &ldquo;Select any alpha character.&rdquo;<\/p>\n<p style=\"padding-left: 30px\">Select-String -pattern &#8220;\\w&#8221;<\/p>\n<p>Now we pipe the results of that to yet another <strong>Foreach-Object<\/strong>. This time we are looking for the lines that the previous command returned. Attach <strong>.line<\/strong> to the <strong>$_<\/strong> in all <strong>ForEach-Object<\/strong> commands and encapsulate that in braces:<\/p>\n<p style=\"padding-left: 30px\">| ForEach-Object { $_.line }<\/p>\n<p>Now that we have all the commands necessary to format our data, we are going to roll them together:<\/p>\n<p style=\"padding-left: 30px\">Get-Content &#8220;.\\over30dayprofiles.txt&#8221; | ForEach-Object {<\/p>\n<p style=\"padding-left: 30px\">$_ -replace &#8216;Administrator&#8217;, &#8221; `<br \/> &nbsp;&nbsp; -replace &#8216;LocalService&#8217;, &#8221; `<br \/> &nbsp;&nbsp; -replace &#8216;NetworkService&#8217;, &#8221; `<\/p>\n<p style=\"padding-left: 30px\">} | Select-String -Pattern &#8220;\\w&#8221; | ForEach-Object { $_.line }<\/p>\n<p><span style=\"font-size: 12px\">Now, we redefine the variable <\/span><strong style=\"font-size: 12px\">$over30dayprofiles<\/strong><span style=\"font-size: 12px\">. We do this by encapsulating the entire previous script in paretheses, and preceding that with the &ldquo;at&rdquo; sign (@)<\/span><strong style=\"font-size: 12px\"> <\/strong><span style=\"font-size: 12px\">to create an array:<\/span><\/p>\n<p style=\"padding-left: 30px\"><strong>$over30dayprofiles = @(<\/strong>Get-Content &#8220;.\\over30dayprofiles.txt&#8221; | Foreach-Object {<\/p>\n<p style=\"padding-left: 30px\">$_ -replace &#8216;Administrator&#8217;, &#8221; `<br \/> &nbsp;&nbsp; -replace &#8216;LocalService&#8217;, &#8221; `<br \/> &nbsp;&nbsp; -replace &#8216;NetworkService&#8217;, &#8221; `<\/p>\n<p style=\"padding-left: 30px\">} | Select-String -Pattern &#8220;\\w&#8221; | ForEach-Object { $_.line }<strong>)<\/strong><\/p>\n<p><span style=\"font-size: 12px\">If you want to check your work, type <\/span><strong style=\"font-size: 12px\">$over30dayprofiles<\/strong><span style=\"font-size: 12px\">. It should return a list of profiles that have not been accessed within the past 30 days, with the exception of our defined profiles. &nbsp;<\/span><\/p>\n<p>Now that we have our input, we can proceed with our script. To sequentially delete profiles, we need to set up a <strong>Do-Until<\/strong> loop. This kind of loop simply states, &ldquo;Do X, until Y is True.&rdquo; For our loop we need to define two variables. The first is the counter, <strong>$i<\/strong>, and the second is the <strong>Until<\/strong> condition (Y).<\/p>\n<p style=\"padding-left: 30px\"><strong>Note<\/strong>&nbsp;&nbsp;&nbsp;A counter is a value in programming that is used to faciliate an end to a programming loop. Without it, the program would constantly &ldquo;Do&rdquo; something until the computer is shut off or the process is ended somehow.<\/p>\n<p>I always start by defining <strong>$i<\/strong> as 0:<\/p>\n<p style=\"padding-left: 30px\">$i = 0<\/p>\n<p>The <strong>Count<\/strong> variable is merely the number of profiles in our <strong>$over30dayprofiles <\/strong>array<strong>. <\/strong>To define the number of items in an array, we append the count array property, <strong>.count<\/strong>, to <strong>$over30dayprofiles<\/strong>,<strong> <\/strong>and we use that to define the<strong> $count <\/strong>variable:<\/p>\n<p style=\"padding-left: 30px\">$count = $over30dayprofiles.count<\/p>\n<p>We also need to make sure that our path actually contains profiles by changing our logical location on the hard drive to C:\\Documents and Settings. We do this with the <strong>Set-Location<\/strong> cmdlet:<\/p>\n<p style=\"padding-left: 30px\">Set-Location &lsquo;C:\\Documents and Settings&rsquo;<\/p>\n<p>Now we start our <strong>Do-Until<\/strong> loop. As explained earlier, there are two parts to this loop: the <strong>Do<\/strong> section and the <strong>Until<\/strong> section. Because <strong>Do<\/strong> is followed by an action (and functional code, not simply values), it is followed by braces. <strong>Until<\/strong> is followed by a condition, so we use parentheses instead of braces:<\/p>\n<p style=\"padding-left: 30px\">Do{}<\/p>\n<p style=\"padding-left: 30px\">Until()<\/p>\n<p><span style=\"font-size: 12px\">To remove the subsequent directories, we are going to use the <\/span><strong style=\"font-size: 12px\">Remove-Item<\/strong><span style=\"font-size: 12px\"> cmdlet. When we remove these profiles, it is important &nbsp;that the profile and all child folders and items (regardless of special properties such as Read-only, Hidden, or System) are removed. We do this by using the <\/span><strong style=\"font-size: 12px\">-force<\/strong><span style=\"font-size: 12px\"> and the <\/span><strong style=\"font-size: 12px\">-recurse<\/strong><span style=\"font-size: 12px\"> switches. The <\/span><strong style=\"font-size: 12px\">-force<\/strong><span style=\"font-size: 12px\"> switch ignores special properties, and the <\/span><strong style=\"font-size: 12px\">-recurse<\/strong><span style=\"font-size: 12px\"> switch removes all of the child directories and their content.<\/span><\/p>\n<p style=\"padding-left: 30px\">Remove-Item -force -recurse<\/p>\n<p>By itself, all this command does is indiscriminately delete the following item and all of its content, regardless of properties. What it does not say is actually what to delete. That is where we use the <strong>$over30dayprofiles<\/strong> array. As displayed earlier in this post, this array holds the names of the profiles that have not been accessed within the past 30 days, minus those that we specified.<\/p>\n<p style=\"padding-left: 30px\">Remove-Item -force -recurse $over30dayprofiles<\/p>\n<p>We want to do this sequentially, so we are using the counter that we defined earlier to identify the element in the array that we want to remove. Remember that with an array, you are defining multiple values within a single variable. The values in the array are called elements, and each element is numbered (beginning with 0) to differentiate one value from the next.<\/p>\n<p>Specifing an element within an array is done by appending brackets to the end of an array variable, and putting the element number within those brackets. You can use a variable in place of an actual number. This is where we are using our counter, which conveniently starts at 0 and increments by 1 every time the <strong>Do <\/strong>statement loops.<\/p>\n<p style=\"padding-left: 30px\">Remove-Item -force -recurse $over30dayprofiles[$i]<\/p>\n<p>We now add the counter. Simply appending two addition symbols (<strong>++<\/strong>) to the <strong>$i<\/strong> variable will increment it by one:<\/p>\n<p style=\"padding-left: 30px\">Remove-Item -force -recurse $over30dayprofiles[$i]<br \/> $i++<\/p>\n<p><span style=\"font-size: 12px\">Now that we have completed our statement, we need to nest it in the braces following <\/span><strong style=\"font-size: 12px\">Do<\/strong><span style=\"font-size: 12px\">:<\/span><\/p>\n<p style=\"padding-left: 30px\">Do{<\/p>\n<p style=\"padding-left: 30px\">Remove-Item -force -recurse $over30dayprofiles[$i]<br \/> $i++<\/p>\n<p style=\"padding-left: 30px\">}<\/p>\n<p style=\"padding-left: 30px\">Until()<\/p>\n<p><span style=\"font-size: 12px\">Without defining the <\/span><strong style=\"font-size: 12px\">Until<\/strong><span style=\"font-size: 12px\"> condition, this loop will continue until it starts generating errors (when it runs out of elements in the array to perform the <\/span><strong style=\"font-size: 12px\">Do<\/strong><span style=\"font-size: 12px\"> action against). The following error message tells you where the issue is: &ldquo;Missing While or <\/span><strong style=\"font-size: 12px\">Until in Do<\/strong><span style=\"font-size: 12px\"> loop.&rdquo; Always read your error messages.<\/span><\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/1273.6-9-13-3.png\"><img decoding=\"async\" title=\"Image of message\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/1273.6-9-13-3.png\" alt=\"Image of message\" \/><\/a><\/p>\n<p>Right now our <strong>Until<\/strong> statement looks like this:<\/p>\n<p style=\"padding-left: 30px\">Until()<\/p>\n<p>Our condition is going to say <strong>Do<\/strong> the above <strong>Until <\/strong>the counter is greater than the number of items in <strong>$over30dayprofiles ($count).<\/strong>&rdquo; Here is where <strong>Until<\/strong>, our counter, and <strong>$count<\/strong> come together. First we express the counter:<\/p>\n<p style=\"padding-left: 30px\">Until($i)<\/p>\n<p>Then we use the greater than (<strong>-ge<\/strong>) inequality:<\/p>\n<p style=\"padding-left: 30px\">Until($i -ge)<\/p>\n<p>And finally, we insert the <strong>$count<\/strong> directly after the greater than operator for the complete conditon that reads, &ldquo;Until our counter is greater than the count.&rdquo;<\/p>\n<p style=\"padding-left: 30px\">Until($i -ge $count)<\/p>\n<p>Combined, the script looks like this:<\/p>\n<p style=\"padding-left: 30px\">IF ((Get-WmiObject Win32_OperatingSystem).version &ndash;like &ldquo;5*&rdquo;) {<\/p>\n<p style=\"padding-left: 30px\">$over30dayprofiles = @(Get-ChildItem -force &#8220;C:\\Documents and Settings&#8221; | Where {<\/p>\n<p style=\"padding-left: 30px\">((Get-Date)-$_.lastwritetime).days -ge 30<\/p>\n<p style=\"padding-left: 30px\">} )<\/p>\n<p style=\"padding-left: 30px\">}<\/p>\n<p style=\"padding-left: 30px\">IF ((Get-WmiObject Win32_OperatingSystem).version &ndash;like &ldquo;6*&rdquo;) {<\/p>\n<p style=\"padding-left: 30px\">$over30dayprofiles = @(Get-ChildItem -force &#8220;C:\\Users&#8221; | Where {<\/p>\n<p style=\"padding-left: 30px\">((Get-Date)-$_.lastwritetime).days -ge 30<\/p>\n<p style=\"padding-left: 30px\">} )<\/p>\n<p style=\"padding-left: 30px\">Set-Content &#8220;.\\over30dayprofiles.txt&#8221; $over30dayprofiles<\/p>\n<p style=\"padding-left: 30px\">$over30dayprofiles = @(Get-Content &#8220;.\\over30dayprofiles.txt&#8221; | Foreach-Object {<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; &nbsp;$_ -replace &#8216;Administrator&#8217;, &#8221; `<br \/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -replace &#8216;LocalService&#8217;, &#8221; `<br \/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -replace &#8216;NetworkService&#8217;, &#8221;<\/p>\n<p style=\"padding-left: 30px\">} | Select-String -Pattern &#8220;\\w&#8221; | ForEach-Object { $_.line })<\/p>\n<p style=\"padding-left: 30px\">$count = $over30dayprofiles.count<\/p>\n<p style=\"padding-left: 30px\">$i = 0<\/p>\n<p style=\"padding-left: 30px\">Set-Location &#8216;C:\\Documents and Settings&#8217;<\/p>\n<p style=\"padding-left: 30px\">Do {<\/p>\n<p style=\"padding-left: 30px\">&nbsp; Remove-Item -force -recurse $over30dayprofiles[$i]<\/p>\n<p style=\"padding-left: 30px\">&nbsp; $i++<\/p>\n<p style=\"padding-left: 30px\">}<\/p>\n<p style=\"padding-left: 30px\">Until($i -ge $count)<\/p>\n<p><span style=\"font-size: 12px\">The beauty of technology is its fluid nature. This script, much like the script in <\/span><a style=\"font-size: 12px\" href=\"http:\/\/blogs.technet.com\/b\/heyscriptingguy\/archive\/2013\/06\/08\/weekend-scripter-use-powershell-to-generate-a-recent-profile-report.aspx\" target=\"_blank\">Use PowerShell to Generate a Recent Profile Report<\/a><span style=\"font-size: 12px\"> will need to be altered as time goes by, new versions of operating systems are released, and old versions lose support. I stand by the following statement: &ldquo;There is always another better way to do things.&rdquo; That is especially true with Windows PowerShell. Thank you for reading and, as always, please post your comments in the following <\/span><strong style=\"font-size: 12px\">Leave a Comment<\/strong><span style=\"font-size: 12px\"> text box!<\/span><\/p>\n<p>~Bob<\/p>\n<p>Awesome job, Bob. These are great posts. Thank you for taking time to share with the Windows PowerShell community. Join me tomorrow when I will have a guest blog written by Brian Wilhite. Have a look at his <a href=\"http:\/\/blogs.technet.com\/b\/heyscriptingguy\/archive\/tags\/brian+wilhite\/\" target=\"_blank\">previous Hey, Scripting Guy! Blog posts<\/a>.<\/p>\n<p>I invite you to follow me on <a href=\"http:\/\/bit.ly\/scriptingguystwitter\" target=\"_blank\">Twitter<\/a> and <a href=\"http:\/\/bit.ly\/scriptingguysfacebook\" target=\"_blank\">Facebook<\/a>. If you have any questions, send email to me at <a href=\"mailto:scripter@microsoft.com\" target=\"_blank\">scripter@microsoft.com<\/a>, or post your questions on the <a href=\"http:\/\/bit.ly\/scriptingforum\" target=\"_blank\">Official Scripting Guys Forum<\/a>. See you tomorrow. Until then, peace.<\/p>\n<p><strong>Ed Wilson, Microsoft Scripting Guy<\/strong><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Summary: Guest blogger, Bob Stevens, talks about how to use Windows PowerShell to perform conditional user profile removal. Microsoft Scripting Guy, Ed Wilson, is here. Today we are lucky to have guest blogger, Bob Stevens, return for another exciting and useful blog post. Be sure you check out Bob&#8217;s other Hey, Scripting Guy! Blog posts. [&hellip;]<\/p>\n","protected":false},"author":596,"featured_media":87096,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[424,16,56,3,198,61,45],"class_list":["post-3432","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-scripting","tag-bob-stevens","tag-desktop-management","tag-guest-blogger","tag-scripting-guy","tag-users","tag-weekend-scripter","tag-windows-powershell"],"acf":[],"blog_post_summary":"<p>Summary: Guest blogger, Bob Stevens, talks about how to use Windows PowerShell to perform conditional user profile removal. Microsoft Scripting Guy, Ed Wilson, is here. Today we are lucky to have guest blogger, Bob Stevens, return for another exciting and useful blog post. Be sure you check out Bob&#8217;s other Hey, Scripting Guy! Blog posts. [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts\/3432","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/users\/596"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/comments?post=3432"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts\/3432\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/media\/87096"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/media?parent=3432"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/categories?post=3432"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/tags?post=3432"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}