Conditional User Profile Deletion Revisited

Summary: Guest blogger, Bob Stevens, revises his script for conditional user profile removal.

Microsoft Scripting Guy, Ed Wilson, is here. I am happy to welcome back guest blogger, Bob Stevens. Take it away Bob…

Previously I released a blog post entitled Weekend Scripter: Use PowerShell for Conditional User Profile Removal. If you have not read this post, please do because it is necessary so that you’ll understand what I am talking about today.

It was pointed out to me that Windows Vista and later operating systems create temporary user directories instead of re-creating the directories when the user next signs in. To start at a reference point, here is the original script that I created:

IF ((Get-WmiObject Win32_OperatingSystem).version -like “5*”) {

$over30dayprofiles = @(Get-ChildItem -force “C:Documents and Settings” | Where {

((Get-Date)-$_.lastwritetime).days -ge 30

} )

}

IF ((Get-WmiObject Win32_OperatingSystem).version -like “6*”) {

$over30dayprofiles = @(Get-ChildItem -force “C:Users” | Where {

((Get-Date)-$_.lastwritetime).days -ge 30

} )

Set-Content “.over30dayprofiles.txt” $over30dayprofiles

$over30dayprofiles = @(Get-Content “.over30dayprofiles.txt” | Foreach-Object {

  $_ -replace ‘Administrator’, ” `
   -replace ‘LocalService’, ” `
   -replace ‘NetworkService’, ”

} | Select-String -pattern “w” | ForEach-Object { $_.line })

$count = $over30dayprofiles.count

$i = 0

Set-Location ‘C:Documents and Settings’

Do {

 Remove-Item -force -recurse $over30dayprofiles[$i]

 $i++

}

Until($i -ge $count)

The first alteration that we need is to add a message to the user in the event that there are no profiles to remove. To do so, directly after we remove the desired accounts and white space from the variable $over30dayprofiles, we add the following:

If ($over30dayprofiles.count -eq 0){Write-Host ” all profiles have been accessed in the last 30 days”}

If ($over30dayprofiles.count -gt 0){

Because of the brace that is located at the end of the second IF statement, we need to add a closing brace at the end of the script. This will run the command inside the script only when there are profiles that have not been accessed within the past 30 days.

Directly after that, we add the command to determine the SID of each profile that we are going to remove. First we need to define in a variable the number of profiles that we are going to remove:

$count = $over30dayprofiles.count

The Count property simply counts the items in the $over30dayprofiles array. Next we need to state that this is only to be run in any version of Windows 6 and later. I explained how to do this in another previous post: Weekend Scripter: Use PowerShell for Conditional User Profile Removal.

IF ((Get-WmiObject Win32_OperatingSystem).version -like “6*”) {}

Inside the braces, we are going to use the WMI object Win32_userprofile. To do this, we need to use the Get-WMIobject cmdlet to call Win32_userprofile:

Get-WMIobject win32_userprofile

Because we only want two properties (SID and Path), we are going to filter it by using the pipe operator (|) followed by the Select cmdlet:

Get-WMIobject win32_userprofile | select

The property types are the same for all profiles, so no quotations are needed. We simply need to separate them by a comma and a space:

Get-WMIobject win32_userprofile | Select SID, Localpath

We are going to manipulate this further, so we pipe it into the file named ProfileInfo.txt:

Get-WMIobject win32_userprofile | Select SID, Localpath | Out-File .ProfileInfo.txt

The next portion of the script utilizes a Do-Until loop, so we set a counter at 0:

$i = 0

Now let’s create the item that we are going to be manipulating:

New-Item -path .SID.txt -itemtype File

And finally, we can start the Do-Until loop:

Do{}

Until()

Inside the braces that follow the Do statement, we are going to pull the contents of ProfileInfo.txt, select any string that has the currently selected value from the $over30dayprofiles array, and split the line at the first whitespace that occurs. This is done by first retrieving the data, and then selecting the line:

Get-Content .ProfileInfo.txt | Select-String -pattern $over30dayprofiles[$i] |

Now we split the line at the first white space by using a regular expression:

Foreach-Object { $_ -split ’s+’ }

And we select the first half of the string:

ForEach-Object { $_ -split ‘s+’ | select -f 1}

If we append this command to the end of the content and set the output location, this is the result:

Get-Content .ProfileInfo.txt | Select-String -pattern $over30dayprofiles[$i] | ForEach-Object { $_ -split ‘s+’ | select -f 1} | Add-Content .SID.txt

Now we nest this into the Do portion of our Do-Until loop, and add the escape value. An escape value is a predetermined number that will end the loop when the counter meets the inequality presented by the Until or While statements.

Do {

Get-Content .ProfileInfo.txt | Select-String -pattern $over30dayprofiles[$i] | ForEach-Object { $_ -split ‘s+’ | select -f 1} | Add-Content .SID.txt

$i++}

Until($i -ge $count)

As you can see, the escape value is $i when our counter reaches a value greater than $count. $count is the number of elements in the $over30daysprofiles array.

IF ((Get-WmiObject Win32_OperatingSystem).version -like “6*”) {

Get-WMIobject win32_userprofile | select SID, Localpath | Out-File .ProfileInfo.txt

$i = 0

New-Item -path .SID.txt -itemtype File

DO {

get-content .ProfileInfo.txt | Select-String -pattern $over30dayprofiles[$i] | ForEach-Object {

                        $_ -split ‘s+’ | select -f 1

            } | Add-Content .SID.txt

$i++

}

Until($i -ge $count)

$SID = @(Get-Content .SID.txt)

Now we are going to take the contents of the SID.txt file, and set them as the values for another array, $SID:

$SID = @(Get-Content .SID.txt)

Because this script is to be run on any current computer, we need to add the following line so that we are working out of the Documents and Settings folder:

IF ((Get-WmiObject Win32_OperatingSystem).version -like “5*”) {Set-Location ‘C:Documents and Settings’}

For the sake of clarity, the next set of script will be explained in its entirety.

First we set our counter and Do-Until statement:

$i = 0

Do {}

Until()

Now we add the conditional IF statements:

$i = 0

Do {

            IF(){}

            IF(){}

}

Until()

Next we add our operating system identifiers by using the WMI object we have already been using:

$i = 0

Do {

            IF((Get-WmiObject Win32_OperatingSystem).version -like “5*”){}

            IF((Get-WmiObject Win32_OperatingSystem).version -like “6*”){}

}

Until()

Here is where the script changes depending on the operating system. For version 5 operating systems, we need to simply remove the profile folders because they will re-create if the user signs in again. It was suggested that I use the utility Delprof for this, but because there is no way to differentiate between administrator accounts and user accounts, we will go with this:

$i = 0

Do {

            IF((Get-WmiObject Win32_OperatingSystem).version -like “5*”){

Remove-Item -force -recurse $over30dayprofiles[$i]}
            IF((Get-WmiObject Win32_OperatingSystem).version -like “6*”){}

}

Until()

Now we move on to what actually changed. As expressed in the comments from the previous post, when you delete the profile folder from the Users folder in Windows Vista and later, the operating system will continually generate a temporary profile because the registry key located at:

“HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindows NTCurrentVersionProfileList”

So instead of removing the profile folder or doing some truly interesting stuff with the registry, we are going to use the win32_userprofile WMI object, this bit of script is courtesy of the user, JRV. Specifically, we are going to filter it for the SID in the $SID array that corresponds with the counter, and we are going to set this as the value of the $profile variable:

$profile = Get-WmiObject win32_userprofile -filter “SID=’$($SID[$i])'”

Now that we have the $profile variable defined with the desired SID, we are going to delete the profile that it refers to. This is done with the Delete() property (this will NOT work without the empty parentheses).

$profile.delete()

Now we add these two lines together and nest them in our second IF statement:

$i = 0

Do {

IF((Get-WmiObject Win32_OperatingSystem).version -like “5*”){Remove-Item -force -recurse $over30dayprofiles[$i]}

IF((Get-WmiObject Win32_OperatingSystem).version -like “6*”){ $profile = Get-WmiObject win32_userprofile -filter “SID=’$($SID[$i])'”

 

$profile.delete()}

}

Until()

We need to add the rest of the script to increment the counter:

Do {

IF((Get-WmiObject Win32_OperatingSystem).version -like “5*”){Remove-Item -force -recurse $over30dayprofiles[$i]}

IF((Get-WmiObject Win32_OperatingSystem).version -like “6*”){ $profile = Get-WmiObject win32_userprofile -filter “SID=’$($SID[$i])'”

 

$profile.delete()}

 $i++

}

Until($i -ge $count)

And finally, we need to close the brace that we left open with If “($over30dayprofiles.count -gt 0){“ and clean up our temporary files:

Remove-Item .over30dayprofiles.txt

Remove-Item .ProfileInfo.txt

Remove-Item .SID.txt

}

Following is the complete and working script. Although it will generate errors if there is an issue with the accounts being removed, it will function on all others. These errors are reported, and the script will continue to run to completion. I have used Bold formatting to highlight the sections that have been added. _____________________________________________________________

Set-Location $homedesktop

New-Item -path .over30dayprofiles.txt -itemtype File

IF ((Get-WmiObject Win32_OperatingSystem).version -like “5*”) {

$over30dayprofiles = @(Get-ChildItem -force “C:Documents and Settings” | Where {

((Get-Date)-$_.lastwritetime).days -ge 30

} )

}

IF ((Get-WmiObject Win32_OperatingSystem).version -like “6*”) {

$over30dayprofiles = @(Get-ChildItem -force “C:Users” | Where {

((Get-Date)-$_.lastwritetime).days -ge 30

} )

}

Set-Content “.over30dayprofiles.txt” $over30dayprofiles

 

$over30dayprofiles = @(Get-Content “.over30dayprofiles.txt” | Foreach-Object {

 

  $_ -replace ‘Administrator’, ” `

  -replace ‘LocalService’, ” `

  -replace ‘NetworkService’, ”`

  -replace ‘desktop.ini’, ”`

  -replace ‘temp’, ”`

  -replace ‘All Users’, ”`

  -replace ‘Default’, ”`

  -replace ‘Default User’, ”`

  -replace ‘Public’, ”`

  -replace ‘ User’, ”

 

} | Select-String -Pattern “w” | ForEach-Object { $_.line })

If ($over30dayprofiles.count -eq 0){Write-Host “all profiles have been accessed in the last 30 days”}

If ($over30dayprofiles.count -gt 0){

$count = $over30dayprofiles.count

IF ((Get-WmiObject Win32_OperatingSystem).version -like “6*”) {

get-WMIobject win32_userprofile | select SID, Localpath | Out-File .ProfileInfo.txt

$i = 0

New-Item -path .SID.txt -itemtype File

DO {

Get-Content .ProfileInfo.txt | Select-String -pattern $over30dayprofiles[$i] | ForEach-Object {

                        $_ -split ‘s+’ | select -f 1

            } | Add-Content .SID.txt

$i++

}

 

Until($i -ge $count)

$SID = @(Get-Content .SID.txt)}

IF ((Get-WmiObject Win32_OperatingSystem).version -like “5*”) {Set-Location ‘C:Documents and Settings’}

 

$i = 0

Do {

  IF((Get-WmiObject Win32_OperatingSystem).version -like “5*”){

   Remove-Item -force -recurse $over30dayprofiles[$i]

  }

 

  IF((Get-WmiObject Win32_OperatingSystem).version -like “6*”) {

   $profile = Get-WmiObject win32_userprofile -filter “SID=’$($SID[$i])'”

   $profile.Delete()

  }

 $i++

}

Until($i -ge $count)

 

Remove-Item .over30dayprofiles.txt

Remove-Item .ProfileInfo.txt

Remove-Item .SID.txt

}

The complete script is uploaded to the Script Center Repository: Over 30 Day Removal. Please download it from there, rather than attempting to copy from this web page. As with my previous posts, I welcome all critiques. It is my firm belief that anything can be improved by the collective knowledge of the community.

~Bob

Thank you, Bob, for your time and efforts in writing this blog to share with our readers.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy