Weekend Scripter: Use PowerShell for Conditional User Profile Removal


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's other Hey, Scripting Guy! Blog posts. Bob is a member of the Twin Cities PowerShell User Group. Here is his contact information:

Blog: Help! I’m Stuck in My PowerShell!
Twitter: @B_stevens6
LinkedIn: Robert Stevens

Note   This is the second post in a two part series about using Windows PowerShell to work with user profiles. You should read yesterday’s post, Use PowerShell to Generate a Recent Profile Report, prior to reading today’s.

And now, here’s Bob…

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:

Image of command output

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.

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’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.

Get-Childitem -force "C:\Documents and Settings"

Here you see the Get-Childitem cmdlet followed by the -force 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.

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 (-le) to greater than (-ge), and we change the number of days from 31 to 30.

Get-ChildItem -force "C:\Documents and Settings" | Where {

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

}

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 (@).

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

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

} )

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, Use PowerShell to Generate a Recent Profile Report, so feel free to backtrack if you want to furthur understand them.

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

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

Inside the braces that follow the first IF statement’s condition, we need to nest our variable declaration $over30dayprofiles.

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

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

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

} )

}

We now nest the same variable in the second IF statement’s declaration with one minor variation. We replace “Documents and Settings” with “Users.”

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

$over30dayprofiles = @(Get-ChildItem -force "C:\Users" | Where {

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

} )

}

Roll them together to have a complete picture of our array declaration:

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

} )

}

Combined, our pair of IF statements declare our variable, $over30dayprofiles, as all items with a last write time of greater than 30 days. To manipulate this data into the correct format, we use the Set-Content cmdlet. Set-content 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 Add-Content.) With that, we have the following command:

Set-Content ".\over30dayprofiles.txt" $over30dayprofiles

Here is the result:

Image of command output

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 Get-Content cmdlet:

Get-Content ".\over30dayprofiles.txt"

Now we use the pipe operator (|) to funnel the content of over30dayprofiles.txt  into the ForEach-Object cmdlet.

Get-Content ".\over30dayprofiles.txt" | ForEach-Object

ForEach-Object is simply stating, “For each object, do this.” The “do this” must be encapsulated in braces because it is followed by actual script.

Get-Content ".\over30dayprofiles.txt" | ForEach-Object{}

Inside the braces, we replace Administrator, LocalService, and NetworkService (with any other profiles you want to exempt) with  a null value by using two single quotation marks with nothing between them (‘’). This is done with the -replace operator.

Note   Remember that you need to have the placeholder, $_, on the first line because it is necessary for the operation of ForEach-Object. The placeholder, $_, simply tells Windows PowerShell to address each individual object within the data provided.

$_ -replace 'Administrator', '' `
  -replace 'LocalService', '' `
  -replace 'NetworkService', '' `

It is important to clarify the structure of the -replace operator. -Replace must be followed by the item to be replaced, which is encapsulated in single quotation marks() and followed by a comma (,). The comma is followed by what is being replaced, and it is also encapsulated in single quotation marks.

Note   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.

Finally, we use the accent (`) character to signify “next line.” As you can see, only the first line requires the placeholder.

So far, this set of script looks like this:

Get-Content ".\over30dayprofiles.txt" | ForEach-Object {

$_ -replace 'Administrator', '' `
  -replace 'LocalService', '' `
  -replace 'NetworkService', '' `

  }

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 Select-String cmdlet and a regular expression. We start with the pipe operator and the Select String cmdlet:

| Select-String

Select-String 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 -pattern definition followed by the Word regular expression (\w). The Word regular expression defines an alphabetical character. The following script states, “Select any alpha character.”

Select-String -pattern "\w"

Now we pipe the results of that to yet another Foreach-Object. This time we are looking for the lines that the previous command returned. Attach .line to the $_ in all ForEach-Object commands and encapsulate that in braces:

| ForEach-Object { $_.line }

Now that we have all the commands necessary to format our data, we are going to roll them together:

Get-Content ".\over30dayprofiles.txt" | ForEach-Object {

$_ -replace 'Administrator', '' `
   -replace 'LocalService', '' `
   -replace 'NetworkService', '' `

} | Select-String -Pattern "\w" | ForEach-Object { $_.line }

Now, we redefine the variable $over30dayprofiles. We do this by encapsulating the entire previous script in paretheses, and preceding that with the “at” sign (@) to create an array:

$over30dayprofiles = @(Get-Content ".\over30dayprofiles.txt" | Foreach-Object {

$_ -replace 'Administrator', '' `
   -replace 'LocalService', '' `
   -replace 'NetworkService', '' `

} | Select-String -Pattern "\w" | ForEach-Object { $_.line })

If you want to check your work, type $over30dayprofiles. It should return a list of profiles that have not been accessed within the past 30 days, with the exception of our defined profiles.  

Now that we have our input, we can proceed with our script. To sequentially delete profiles, we need to set up a Do-Until loop. This kind of loop simply states, “Do X, until Y is True.” For our loop we need to define two variables. The first is the counter, $i, and the second is the Until condition (Y).

Note   A counter is a value in programming that is used to faciliate an end to a programming loop. Without it, the program would constantly “Do” something until the computer is shut off or the process is ended somehow.

I always start by defining $i as 0:

$i = 0

The Count variable is merely the number of profiles in our $over30dayprofiles array. To define the number of items in an array, we append the count array property, .count, to $over30dayprofiles, and we use that to define the $count variable:

$count = $over30dayprofiles.count

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 Set-Location cmdlet:

Set-Location ‘C:\Documents and Settings’

Now we start our Do-Until loop. As explained earlier, there are two parts to this loop: the Do section and the Until section. Because Do is followed by an action (and functional code, not simply values), it is followed by braces. Until is followed by a condition, so we use parentheses instead of braces:

Do{}

Until()

To remove the subsequent directories, we are going to use the Remove-Item cmdlet. When we remove these profiles, it is important  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 -force and the -recurse switches. The -force switch ignores special properties, and the -recurse switch removes all of the child directories and their content.

Remove-Item -force -recurse

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 $over30dayprofiles 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.

Remove-Item -force -recurse $over30dayprofiles

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.

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 Do statement loops.

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

We now add the counter. Simply appending two addition symbols (++) to the $i variable will increment it by one:

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

Now that we have completed our statement, we need to nest it in the braces following Do:

Do{

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

}

Until()

Without defining the Until condition, this loop will continue until it starts generating errors (when it runs out of elements in the array to perform the Do action against). The following error message tells you where the issue is: “Missing While or Until in Do loop.” Always read your error messages.

Image of message

Right now our Until statement looks like this:

Until()

Our condition is going to say Do the above Until the counter is greater than the number of items in $over30dayprofiles ($count).” Here is where Until, our counter, and $count come together. First we express the counter:

Until($i)

Then we use the greater than (-ge) inequality:

Until($i -ge)

And finally, we insert the $count directly after the greater than operator for the complete conditon that reads, “Until our counter is greater than the count.”

Until($i -ge $count)

Combined, the script looks like this:

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 beauty of technology is its fluid nature. This script, much like the script in Use PowerShell to Generate a Recent Profile Report 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: “There is always another better way to do things.” That is especially true with Windows PowerShell. Thank you for reading and, as always, please post your comments in the following Leave a Comment text box!

~Bob

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 previous Hey, Scripting Guy! Blog posts.

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

Comments (29)

  1. jrv says:

    There are two reliable ways to remove profiles remotely

    Win32_UserProfile.Delete()  (Vista and later)

    AND

    DELPROF (pre-Vista)

    These both use the internal API to remove the profiles.  The removal is done in the system context and makes all safety checks before attempting to remove the profile.  If a profile is hung in the registry manual methods will corrupt the profile and make it impossible to remove without a restart.  

    Win32_UserProfile can also change the owner of a profile.  This is useful if you want to rename a folder that has become decorated due to a collision or hung registry hive.

    There is really no need to clean the registry on a properly working system.  The registry entry is only a hint.  If the registry entry points nowhere the profile will be regenerated or a temporary profile will be generated which is what you will see when a profile has become corrupted due to a failed attempt to do a manual deletion or due to bad security settings on a roaming profile folder share root folder.

  2. jrv says:

    @Bob

    Hah! You found it before I did.

    Now we just have to figure out why Microsoft refuses to updated the WMI documentation…???!!???

  3. jrv says:

    @Bob Stevens

    That is easy

    $profile = Get-WmiObject win32_userprofile -filter "SID='$SID[$i]'"

    You must use the double quotes on the outside or it won't convert.  You also need to force the evaluation.

    # some dummy strings for testing

    $SID='sidly1','sidly2','sidly3'

    $I = 1

    # your way

    PS C:scripts> 'SID="$SID[$i]"'

    SID="$SID[$i]"

    #the PowerShell way

    PS C:scripts> "SID='$($SID[$i])'"

    SID='sidly2'

  4. jrv says:

    @Bob Stevens

    I am still not sure what problem you are trying to solve.

    Profiles have a type which can be used to return non-system profiles.  SIDS can be discovered from many locations.  We can get SIDS by user account name using Win32_Account

    gwmi Win32_Account|select Caption, SID,SidType

    We can get only domain accounts by pointing at a domain controller.

    gwmi Win32_Account -computer dc1 |select Caption, SID,SidType

  5. jrv says:

    @Bob Stevens

    This is why I always note that DELPROF or Win32_UserProfile.Delete are the only reliable ways to delete a profile.  Start there.  If that returns an error then go clean up the folders.  Both will always (in my experience) delete the correct registry key but can fail on removing the folders.  Both methods are designed to use the correct profiles APIs for their respective platforms.  Both always resolve all issues of architecture.

  6. jrv says:

    @Bob Stevens- sometimes good things come with big headaches.  Sorry about that.

  7. jrv says:

    @Bob –

    Profile management comes up frequently for some reason.  Here are some things learned over a number of years fixing damage done by Cowboy Admins.

    It is perfectly OK to delete a profile on all versions of Windows if the profile is a roaming profile.  It will be regenerated on next login from the roamed copy.  Deleting the registry key will have no effect on this behavior.

    Local profiles may behave differently on Vista and later and the key existence can affect this. It doesn't always and I have not yet tracked down why.

    The bigger issue for me is orphaned profiles.  If a profile becomes locked due to a hanging registry hive a second logon can cause a new profile to be generated.  This can start a cascade of issues.  Currently I just manually delete profiles and keys.  If the profile is roamed I validate the master profile with CProfile.

    I strongly recommend using redirected folders for ALL re-directable folders.  This can prevent a huge amount of profile issues and will help you when it is time to migrate to a new platform.  Usually this improves performance and reliability.

    On Windows 7 and later we have been using  mostly local profiles with all folders redirected.  On a Terminal Server I roam the profile and set GP to delete the local copy.  This reduces some demand on resources and assures a clean profile as long as we check the event log for failures.

    Many of these things have helped to avoid profile maintenance on larger systems.

  8. jrv says:

    @Pete – the code runs in the highest context.  If permissions are corrupted it is either disk corruption, A rogue program or a bad source profile if roamed.  There is no standard fix for this.  You need to fix each one manually.  I have a set of scripts that can be run but in many cases I have to do it manually and do a 'Safe Boot' to succeed.

  9. jrv says:

    @Bob

    Delete exists but is poorly documented.  Somewhere is a KB recommending that it is the replacement for DELPROF.  I have used it since Vista.

    We can see the 'Delete' method with PowerShell easily.

    PS C:scripts> $profile=gwmi win32_userprofile -filter 'SID="S-1-5-21-714214563-99999999-2839987549-1001"'

    PS C:scripts> $profile.Delete

    OverloadDefinitions

    ——————-

    void Delete()

    void Delete(System.Management.DeleteOptions options)

    void Delete(System.Management.ManagementOperationObserver watcher)

    void Delete(System.Management.ManagementOperationObserver watcher, System.Management.DeleteOptions options)

    Note the 'DeleteOptions'

    PS C:scripts> $opt=New-Object System.Management.DeleteOptions

    PS C:scripts> $opt

    Context                                                     Timeout

    ——-                                                     ——-

    {}                                                          10675199.02:48:05.4775807

    You do not need to use this unless you want to timeout the operation.  A delete can take some time if the profile is large.

  10. jrv says:

    @Bob Stevens

    You fail to say what problem it is that you are trying to solve.

  11. jrv says:

    @All

    Microsoft does need to update Win32_UserProfile to add 'Copy', 'Export', and 'Import' methods.

    We use copy to create a base profile from a template.  Import and export manage the non-volatile and roam-able portions of the profile.

    Another method that would be useful is a "Create" method which would create the profile without the user logging in.  I believe this is available in the deployment tools.

  12. ceasley says:

    Where are you removing the profiles from the registry?  We have found in our environment that in Windows 7 if you remove the profile folder without removing it from the registry, it logs the user in with a temporary profile.

  13. Ian says:

    Now you also need to remove the profiles from the registry, and, if the profiles are local, from the local user account database.

  14. Pete Gomersall says:

    I agree with ceasley; it has broken logic for Windows 78. If you don't remove registry entries for the users then will not be able to logon with a maintainable profile.

    Also remove-item even with -force is very fragile and often breaks if you use to remove profile folders. This is usually caused by +260 character limit been exceeded – often from web passed files Flash etc. are common.

    I personally prefer to use invoke-command to call cmd's rd function. Even this often doesn't remove the folder at the first attempt so it is best to use test-path to check; then run the removal code again to finally delete the folder. Strangely I have never found it it takes more that 2 calls to remove the folder this way.

    Pete

  15. Bob Stevens says:

    Everyone,

    Wow!  What a response!  This is what I love about tech communities.  It seems that while my script works, It has a disconnect in windows 7/8 as far as the registry is concerned.  Kudos to the catch!  User profiles are listed in:

    HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsNTCurrentVersionProfileList

    This key is present in both Version 5 and Version 6 of windows, but as you have stated, it does cause an issue in version 6 of windows if you remove the actual profile folder without removing the key you will run into the temporary profile issue that has been brought up by ceasley, Ian, and Pete Gomersall.  

    The best solution That has been presented to me is by JRV, that is using the

    Win32_UserProfile.Delete()  (Vista and later)

    AND

    DELPROF (pre-Vista)

    I will always stand by my statement “There is always a better way” and you have proven me right.  As soon as I update the script with the suggestions, I will update it here.

    Thank You,

    Bob Stevens

  16. Pete Gomersall says:

    Unfortunately there is no such method – Win32_UserProfile.Delete(). The only method for this class is ChangeOwner?

  17. Bob Stevens says:

    "Pete Gomersall 10 Jun 2013 8:17 AM

    Unfortunately there is no such method – Win32_UserProfile.Delete(). The only method for this class is ChangeOwner?"

    While this does not exist for Windows XP, I have not been able to verify for Windows vista and up.  As soon as I do I will post a comprehensive update to the script.  If you have any suggestions, I would be happy to discuss them!

    Thank You,

    Bob Stevens

  18. Ed Wilson says:

    @JRV you are correct. The UserProfileProvider in Vista (and above) is updated to support instance delete. Here is a KB article that talks about this. support.microsoft.com/…/930955

  19. Bob Stevens says:

    @bob @jrv,

    Well that discounts the lengthy regedit script I was working on for this (Headache and a half).  Now to figure out how to implement it within the above script effectively.

    I suspect the best place to put it is:

    Do {

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

                      (Begin If statement for version 6 HERE<———————————————

                     (Put Win32_UserProfile associated code HERE) <———————————–

                       (End If statement for version 6 HERE<———————————————-

    $i++

    }

  20. Pete Gomersall says:

    @jrv – I stand up and salute you; obviously we can't believe everything Microsoft says. Hat's off to you for pursuing the problem.

    All have checked out the method works doing tests on a range of profiles – however it doesn't always. In some case the users profile permissions become corrupted and the script removes the registry settings and all the files it can then errors. I believe if you run the script as SYSTEM it would have greater chance of success, however I can't find a profile to test on with broken permissions to verify against.

  21. Bob Stevens says:

    @jrv, @bob, @pete,

    After reviewing the Win32_UserProfile WMI object, it seems to give me the same stuff that the registry gives me, in much the same format.  With that being said, In order to get this script to work correctly I need to generate a list of registry entries that corresponds to the profiles being removed.  This leads to the following question, What happens if we indiscriminately remove ALL of the registry entries in HEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsNTCurrentVersionProfileList?  Will Windows simply recreate the entries, or will it fail to log in the users?  I am going to test this in a bit, and will let you know.  (Thinking outside the box, maby this will reduce code)

    Thank You,

    Bob Stevens

  22. Bob Stevens says:

    @jrv, @pete, @Ed,

    As we know there are two distinct user account types, Domain and local.  As such I decided to test the following on both:

    Delete the key without deleting the profile

    Delete the key and the profile

    If you delete the key without deleting the profile then a new profile is created that looks like this:

    User1.Domain1 <———Domain

    User1.Computer1<———Local

    If you delete the key and the profile then a new profile is created when the user logs in:

    User 1 <———Domain

    User 1 <———Local

    The first solution works, but  has the unintended consequence of recreating all user profiles, so if the user profile is  not deleted, then it shows up in the profile folder as

    User 1

    User 1.Domain1

    OR

    User1

    User1.Computer1

    This defeats the intent of the above script as it recreates all of the profiles if we just empty the key HEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsNTCurrentVersionProfileList to begin with.  

    Long story short: Dead End.

  23. Bob Stevens says:

    @jrv

    Using a combination of the commands you suggested, and my own code, I am close to finding a solution.  As you know, more code equates to more problems.  when I have completed it I will let all of you know.

    Thank You,

    Bob Stevens

  24. Bob Stevens says:

    @JRV @ED,

    I apologize for the delay, Work has been busy.  So straight to it.  Earlier in the comments you mentioned the following code (I added the Parentheses () to make it work):

    $profile = Get-WmiObject win32_userprofile -filter 'SID="$SID[$i]"'

    $profile.Delete()

    The Problem I am having is with  'SID="$SID[$i]"'.  It will not call the array element.  as a work arround I used the win32_userprofile to generate a list of SID identifiers with:

    get-WMIobject win32_userprofile | select SID, Localpath | out-file .ProfileInfo.txt

    $i = 0

    DO {

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

    $_ -split 's+' | select -f 1

    } | Add-Content .SID.txt

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

    I then use this list to selectively remove the registry entries.  While this will work if you are the Builtin Administrator (Which most people are not)  It will return permission errors if you are not.

    So that leaves me with two options:

    1. find a way to make $profile = Get-WmiObject win32_userprofile -filter 'SID="$SID[$i]"' work

    2. recursively alter the permissions on the profiles in question

    It goes without saying that I prefer the former option.  provided that with the communities assistance we can get it to work, It will reduce the required code rather than increase it due to ACL alteration code.

  25. Bob Stevens says:

    @Jrv,

    The problem lies with:

    $profile = Get-WmiObject win32_userprofile -filter 'SID="$SID[$i]"'

    If you replace 'SID="$SID[$i]"' with 'SID="SOME SPECIFIED SID"' Then you can call the profile and delete it with $profile.delete().  

    The fact is that you cannot call the profile using an element of an array.  this script is intended to be used by local technicians as the move from computer to computer.

  26. Bob Stevens says:

    @JRV,

    Aside from a pesky error code that does not effect anything, The script now works beautifully.  I need to have it tested ~20 times before I will release it due to its potentionally destructive nature.  Thank you so much for your help.

    -Bob Stevens

  27. dg says:

    problem in a very specific scenario…

    I am using the delete_ method in a VB script that runs during OS deployment, during the RunSycnchonous specialize phase to delete to local administrator profile.  its a very simple script and it works on every system we build*.  

    *EXCEPT x86 ATOM based EFI systems.  these systems (I have two, a Lenovo ThinkPad 2, and an HP ElitePad)

    I am deploying Win8 or Win8.1 to them.

    The profile does get deleted, but the problem I have is that the deletion, for whatever reason, is causing the network card to become 'disconnected'.  I can disable/enable the card in device manager (during windows setup), and the network comes back online, but this is creating a real problem.  I cannot build these systems since I can't on the fly enable/disable the cards.

    does anyone have any idea what this would happen?  the network card on the docks for these devices is a USB based SMSC LAN9512, and the latest driver is installed.

    it took forever to isolate the issue to the delete_ command inside the VB script.  now I need to know why!!!!

  28. Sri says:

    Wouldn’t the best way of doing this be the use of the Group Policy for "Delete user profiles older than a specified number of days on system restart" (Vista & later).

    There is one gotcha that I found with this one: I ran into a situation where the system images were set up so that the Default User profile (which is used as the template for when a new user logs into a particular PC for the first time) was something other
    than the usual default profile… and then this policy deleted that & that in turn prevented new users from being able to log in on the system.

  29. alan says:

    How I can log the specifics profiles that were removed by this script, witch command and line I could use/put in the script, please ?

Skip to main content