none
Reversal For Richard Mueller's Powershell AD (544 Disable) Script from Text Log File RRS feed

  • Question

  • Hi guys,

    I am a very novice scripter and I have been asked to find/come up with a script to reverse the changes that would be done by Richard Mueller's script which I've modified slightly in order to give some logging features for the client as to what is being changed and on which accounts.

    $Domain = New-Object System.DirectoryServices.DirectoryEntry
    $Searcher = New-Object System.DirectoryServices.DirectorySearcher
    $Searcher.SearchRoot = $Domain
    $Searcher.PageSize = 200
    $Searcher.SearchScope = "subtree"
    
    $Searcher.PropertiesToLoad.Add("distinguishedName") > $Null
    $Searcher.PropertiesToLoad.Add("userAccountControl") > $Null
    $Searcher.PropertiesToLoad.Add("whenCreated") > $Null
    
    # Filter on all users not required to have a password.
    $Searcher.Filter = "(&(objectCategory=person)(objectClass=user)" + "(userAccountControl:1.2.840.113556.1.4.803:=32))"
    $Results = $Searcher.FindAll()
    
    ForEach ($User In $Results)
    {
         #Retrieve values.
        $Useroutput = $null
        [string]$DN = $User.Properties.distinguishedname
        [string]$Flag = $User.Properties.useraccountcontrol
        [string]$FlagOrig = $User.Properties.useraccountcontrol
        [string]$Created = $User.Properties.whencreated
        $CSVFileLocation='c:\scripts\544_test.txt'
        $Useroutput = $DN + "," + $Flag + "," + $Created
    
       $Useroutput 
     
     #    Bind to user object.
        $User = [ADSI]"LDAP://$DN"
     #    Toggle bit off for "Password not required".
        $Flag = $Flag -bxor 32
        $User.userAccountControl = $Flag
     #    Save updated user object.
        $User.SetInfo()
    
      Add-Content -path $CSVFileLocation -value ($DN + "  Original Flag=" + $FlagOrig + "  (Flag Set To: $Flag) " + "  Created On:"+$Created)
       }
    

           



    So what is needed is a script that will take the resulting output file and reverse the changes made to the accounts listed therein.  Preferably this would create another log file which would tell the account information as well as what the flag was set to.

    Any assistance that can be offered on this would be greatly appreciated!

    Regards,
    Matt Miller.

    Wednesday, February 4, 2015 5:46 PM

Answers

  • This is probably the shortest and simplest way:


    # enable "password not required" for users that have it disabled
    get-aduser -filter { PasswordNotRequired -eq $false } |
      set-aduser -PasswordNotRequired $true -passthru |
      select-object -expandproperty DistinguishedName |
      out-file AffectedUsers.txt
    

    Here is how you would reverse it:


    # disable "password not required" for users in list
    get-content AffectedUsers.txt |
      set-aduser -PasswordNotRequired $false
    


    -- Bill Stewart [Bill_Stewart]

    Thursday, February 5, 2015 6:28 PM
    Moderator

All replies

  • The code you posted toggles the ADS_UF_PASSWD_NOTREQD bit (32, 0x20).

    The code $Flag = $Flag -bxor 32 means "toggle the bit" (i.e., if the bit is set, clear it, or if it's cleared, set it).

    So you should be able to run the script again to reverse the effect.


    -- Bill Stewart [Bill_Stewart]

    Wednesday, February 4, 2015 5:53 PM
    Moderator
  • The code you posted toggles the ADS_UF_PASSWD_NOTREQD bit (32, 0x20).

    The code $Flag = $Flag -bxor 32 means "toggle the bit" (i.e., if the bit is set, clear it, or if it's cleared, set it).

    So you should be able to run the script again to reverse the effect.


    -- Bill Stewart [Bill_Stewart]

    Thanks for the info Bill. 

    I've been able to get the script to reverse the change on an account by changing:

    + "(userAccountControl:1.2.840.113556.1.4.803:=32))"

    to

    + "(cn=userAccountIWantToModify))"

     

    However the issue with this is that since the original script is going to be modifying over 8000 accounts affected by the 544 flag, we want to automate reversal on the entire thing if possible.  If I strip out both portions of the code above it would basically run this against the entirety of our AD structure and toggle that bit on for every single account instead of only the ones initially modified.

    So far I've been attempting to have a script which will read the contents of the initial output file and reverse the changes as below but I am not sure where to take it from here:

    foreach ($CN in (Get-Content C:\scripts\544_test.txt))
    {
    $OFS = ','
    [String]$CN,[String]$OU, [String]$DC1, [String]$DC2, [String]$OriginalFlag, [String]$CFlag, [String]$CDate = $CN -split $OFS
    $DN = "$CN,$OU,$DC1,$DC2"
    $CSVFileLocation = "c:\scripts\ADReturn2.txt"
    [string]$Flag = $User.Properties.useraccountcontrol
    write-host "DN = $DN CN = $CN OU = $OU DC1 = $DC1 DC2 = $DC2 Original Flag = $OriginalFlag"
     #    Bind to user object.
        $User = [ADSI]"LDAP://$DN"
     #   # Toggle bit off for "Password not required".
        $Flag = $Flag -bxor 32
        $User.userAccountControl = $Flag
     #   # Save updated user object.
     #   $User.SetInfo()
    
      Add-Content -path $CSVFileLocation -value ("$DN was returned to a value of $Flag")
    }

    I've also slightly changed the initial output file in order to remove the semicolons and have commas as separators for when it is read into the undo script.

    Wednesday, February 4, 2015 6:17 PM
  • I'm not sure what your exact question is, because I think you're not understanding what a bitmap is.

    The userAccountControl attribute (which the code is reading into an integer variable called $Flags) is a bitmap value, which means basically an integer that gets treated like an array of boolean values.

    The integer value of 544 (hex 0x220) means there are two bits set: ADS_UF_PASSWD_NOTREQD (decimal 32, hex 0x20), and ADS_UF_NORMAL_ACCOUNT (decimal 512, hex 0x200).

    If you say $Flags -bxor 32, you are saying "toggle the ADS_UF_PASSWD_NOTREQD bit". You can see this for yourself at the PowerShell prompt:


    PS C:\> 544 -bxor 32
    512
    PS C:\> 512 -bxor 32
    544
    


    -- Bill Stewart [Bill_Stewart]



    Wednesday, February 4, 2015 6:40 PM
    Moderator
  • Yes but...

    This: "(userAccountControl:1.2.840.113556.1.4.803:=32))"

    says to get all accounts that have bit 32 set.  After you unset it the query will not return those users.  The script needs to use a not to get the unset state.

    A mod could easily provide a switch to choose the state.

    #AD - Users - dont require password
    (&(sAMAccountType=805306368)(userAccountControl:1.2.840.113556.1.4.803:=32))

    # Password required
    (&(sAMAccountType=805306368)(!userAccountControl:1.2.840.113556.1.4.803:=32))


    ¯\_(ツ)_/¯


    • Edited by jrv Wednesday, February 4, 2015 6:53 PM
    Wednesday, February 4, 2015 6:53 PM
  • Correct. The point is that userAccountControl is a bitmap value, where each bit in the number represents some state for the account. You don't want to just arbitrarily query or set this value without understanding how to interpret it.

    -- Bill Stewart [Bill_Stewart]

    Wednesday, February 4, 2015 7:06 PM
    Moderator
  • Here's another example. What does userAccountControl mean if it is set to the integer value 546 (hex 222)?

    This means that the ADS_UF_ACCOUNTDISABLE (2), ADS_UF_PASSWD_NOTREQD (decimal 32, hex 0x20), and ADS_UF_NORMAL_ACCOUNT (decimal 512, hex 0x200) bits are set. In other words, this is a disabled account that does not require a password.

    In other words, if you search only for 544, you will miss disabled accounts, accounts that are set with "password never expires," and other things.

    This is because the value 544 represents a set of bits. There may be other bits set that change the value.


    -- Bill Stewart [Bill_Stewart]

    Wednesday, February 4, 2015 7:14 PM
    Moderator
  • The code posted is an old copy done by Richard quite a few years ago.  It can have problems in 2008 and later.

    This version is more reliable.

    # Set password required
    $CSVFile='c:\scripts\enable_pwd.csv'
    
    $searcher =[adsisearcher]'(&(objectCategory=person)(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=32))'
    $searcher.PropertiesToLoad.Add('')
    $searcher.PageSize = 200
    $searcher.FindAll() |
        ForEach-Object{
            $user=[adsi]($_.Path)
            $User.userAccountControl.Value = $User.userAccountControl.Value -bxor 0x20 # 32 decimal bit 5
            $User.CommitChanges()
            $user
        } |
    Select-Object DistinguishedName, @{ N = 'FlagStatus'; E = { 'NoPasswordRequired' } }, WhenCreated,, UseraccountControl |
        Export-Csv $CSVFile -NoTypeInformation
    
    

    Here is the opposite:

    # Set password not required
    $CSVFile = 'c:\scripts\disable_pwd.csv'
    
    $searcher = [adsisearcher]'(&(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=32))'
    $searcher.PropertiesToLoad.Add('')
    $searcher.PageSize = 200
    $searcher.FindAll() |
    ForEach-Object{
        $user = [adsi]($_.Path)
        $User.userAccountControl.Value = $User.userAccountControl.Value -bor 0x20 # 32 decimal bit 5
        #$User.CommitChanges()
        $user
    } |
    Select-Object DistinguishedName, @{ N = 'FlagStatus'; E = { 'NoPasswordRequired' } }, WhenCreated,UseraccountControl |
    Export-Csv $CSVFile -NoTypeInformation
    
    

    The key is that this has nothing to do with UAC 544,  It is all about the bit that haws the value  of32.  Bills is correct.  We must understand what the bitfield (flagfield) actually does and how to manage it. We are toggoling and testing bit 5 of UAC.

    This LDAP meta filter says to test the bit with value 32 or bit 5 and if the bit is set return true.

    userAccountControl:1.2.840.113556.1.4.803:=32


    ¯\_(ツ)_/¯

    Wednesday, February 4, 2015 7:25 PM
  • This does both:

    function Set-UserPWRequired {
        Param (
            [switch]$DontRequirePassword
        )
        if ($DontRequirePassword) {
            $searcher = [adsisearcher]'(&(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=32))'
        } else {
            $searcher = [adsisearcher]'(&(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=32))'
        }
        
        $searcher.PropertiesToLoad.Add('')
        $searcher.PageSize = 200
        $searcher.FindAll() |
        ForEach-Object{
            $user = [adsi]($_.Path)
            if ($DontRequirePassword) {
                $User.userAccountControl.Value = $User.userAccountControl.Value -bor 32
            } else {
                $User.userAccountControl.Value = $User.userAccountControl.Value -bxor 32
            }
            #$User.CommitChanges()
            $user
        }
    } 
    
    Set-UserPWRequired | Select-Object DistinguishedName, 
                @{ N = 'PasswordRequired'; E = { ($_.UserAccountControl[0] -band 0x20) -eq 0x20 } },
                WhenCreated,UseraccountControl |
        Export-Csv $CSVFile -NoTypeInformation
    

    CommitChanges is commented out for testing. Uncomment to make changes stick.


    ¯\_(ツ)_/¯


    • Edited by jrv Wednesday, February 4, 2015 7:44 PM
    Wednesday, February 4, 2015 7:43 PM
  • Here are some handy formulas:

    (bitmap -band mask) -ne 0 - $true if at least one bit in mask is set

    (bitmap -band mask) -eq mask - $true if all bits in mask are set

    (bitmap -band mask) -eq 0 - $true if none of the bits in mask are set

    bitmap = bitmap -bor mask - set all bits in mask

    bitmap = bitmap -band (-bnot mask) - clear all bits in mask

    bitmap = bitmap -bxor mask - toggle bits in mask


    -- Bill Stewart [Bill_Stewart]

    Wednesday, February 4, 2015 7:53 PM
    Moderator
  • I keep copies of most scripts I post, and this one is about 3 years old. There is no way to know which user accounts have been affected by the script posted, except to use the output file. I don't think it makes sense to query for all users where the bit is set, as that will include many users that were not affected by the script.

    The output file is not a csv, so it will need to be parsed to read the user DN. From what I can see, you need to read each line of the file and trim off everything from " Original Flag" on, to retrieve the DN. The remainder of each line is immaterial. Bill is right that we want to -bor the value of userAccountControl with 32 (the bit mask) to set the bit. The result will be that the account will not be required to have a password. It might be appropriate to check first if the bit is already set (-band with 32).


    Richard Mueller - MVP Directory Services


    Thursday, February 5, 2015 1:32 AM
    Moderator
  • I have not tested, but perhaps the following will do the trick:

    $File = "c:\Scripts\544_Test.txt"
    $NewFile = "c:\Script\544_Reset.txt"
    $Lines = (Get-Content $File)
    ForEach ($Line In $lines)
    {
        # Strip off everything after and including the string " Original Flag=".
        $UserDN = $Line.SubString(0, $Line.IndexOf(" Original Flag="))
        # Bind to user object in AD.
        $User = [ADSI]"LDAP://$UserDN"
        $Flag = $User.userAccountControl
        # Set the flag for "password not required".
        $User.userAccountControl -bor 32
        $User.SetInfo()
        Add-Content -Path $NewFile -Value $UserDN
    }
    


    Richard Mueller - MVP Directory Services

    Thursday, February 5, 2015 2:06 AM
    Moderator
  • I would also add that this is simpler if you use the AD cmdlets and specify the -PasswordNotRequired parameter. For example:


    set-aduser kendyer -passwordnotrequired $false

    This disables the ADS_UF_PASSWD_NOTREQD bit for the specified account.


    -- Bill Stewart [Bill_Stewart]

    Thursday, February 5, 2015 2:11 AM
    Moderator
  • Thanks for all the replies folks.  I actually ended up being able to get this worked out yesterday right at the end of day with the following two scripts:

    To disable with log of changes: 

    $Domain = New-Object System.DirectoryServices.DirectoryEntry
    $Searcher = New-Object System.DirectoryServices.DirectorySearcher
    $Searcher.SearchRoot = $Domain
    $Searcher.PageSize = 200
    $Searcher.SearchScope = "subtree"
    
    $Searcher.PropertiesToLoad.Add("distinguishedName") > $Null
    $Searcher.PropertiesToLoad.Add("userAccountControl") > $Null
    $Searcher.PropertiesToLoad.Add("whenCreated") > $Null
    
    # Filter on all users not required to have a password.
    $Searcher.Filter = "(&(objectCategory=person)(objectClass=user)"  + "(userAccountControl:1.2.840.113556.1.4.803:=32))"
    $Results = $Searcher.FindAll()
    
    ForEach ($User In $Results)
    {
         #Retrieve values.
        $Useroutput = $null
        [string]$DN = $User.Properties.distinguishedname
        [string]$Flag = $User.Properties.useraccountcontrol
        [string]$FlagOrig = $User.Properties.useraccountcontrol
        [string]$Created = $User.Properties.whencreated
        $CSVFileLocation='c:\scripts\544_test.txt'
        $Useroutput = $DN + "," + $Flag + "," + $Created
    
       $Useroutput 
     
     #    Bind to user object.
        $User = [ADSI]"LDAP://$DN"
     #    Toggle bit off for "Password not required".
        $Flag = $Flag -bxor 32
        $User.userAccountControl = $Flag
     #   # Save updated user object.
        $User.SetInfo()
    
      Add-Content -path $CSVFileLocation -value ($DN + "," + "  OriginalFlag=" + $FlagOrig + "," + "  (Flag Set To: $Flag), " + "  Created On:"+$Created)
       }
            

    To enable based on output file:

    #------------------------ Constants
    $CSVFileLocation = "c:\scripts\ADReturn.txt"
    $CSVInput = "c:\scripts\544_test.txt"
    $OFS = ','
    #------------------------ End Constants
    $Users = Get-Content $CSVInput
    
    
    $Domain = New-Object System.DirectoryServices.DirectoryEntry
    $Searcher = New-Object System.DirectoryServices.DirectorySearcher
    $Searcher.SearchRoot = $Domain
    $Searcher.PageSize = 200
    $Searcher.SearchScope = "subtree"
    
    $Searcher.PropertiesToLoad.Add("userAccountControl") > $Null
    
    # Filter on all users not required to have a password.
    $Searcher.Filter = "(&(objectCategory=person)(objectClass=user)")
    $Results = $Searcher.FindAll()
    
    
    foreach ($CN in $Users)
    {
    
    
    [String]$CN,[String]$OU, [String]$DC1, [String]$DC2, [String]$OriginalFlag, [String]$CFlag, [String]$CDate = $CN -split $OFS
    $DN = "$CN,$OU,$DC1,$DC2"
    $CN = $Null
    
    #Retrieve values.
        $Useroutput = $null
        [string]$Flag = $User.Properties.useraccountcontrol
        [string]$FlagOrig = $User.Properties.useraccountcontrol
        [string]$Created = $User.Properties.whencreated
    
    
    ForEach ($User In $Results)
    {
    #Retrieve values.
        $Useroutput = $null
        [string]$Flag = $User.Properties.useraccountcontrol
        }
    
    
    
     #    Bind to user object.
        $User = [ADSI]"LDAP://$DN"
     #    Toggle bit off for "Password not required".
        $Flag = $Flag -bxor 32
        $User.userAccountControl = $Flag
     #    Save updated user object.
        $User.SetInfo()
    
    Add-Content -path $CSVFileLocation -value ("$DN was returned to a value of $Flag")
    
    }
    
    
    
         

    I've been able to use the combination of these two scripts to go through and locate users that are flagged with the bit for the password not being required and change that bit (whether a 544, 546, 2080, ect) and output that change to a text file which can then be used to reverse the change for only those that were initially modified by the first script.  I've tested it multiple times and everything has been working.

    I appreciate all of the help and the information provided!

    Thursday, February 5, 2015 5:13 PM
  • The function I posted does all of that in less than half the lines and would be much faster.  It can aslo tke input from a file with a couple of extra lines.


    ¯\_(ツ)_/¯

    Thursday, February 5, 2015 6:22 PM
  • This is probably the shortest and simplest way:


    # enable "password not required" for users that have it disabled
    get-aduser -filter { PasswordNotRequired -eq $false } |
      set-aduser -PasswordNotRequired $true -passthru |
      select-object -expandproperty DistinguishedName |
      out-file AffectedUsers.txt
    

    Here is how you would reverse it:


    # disable "password not required" for users in list
    get-content AffectedUsers.txt |
      set-aduser -PasswordNotRequired $false
    


    -- Bill Stewart [Bill_Stewart]

    Thursday, February 5, 2015 6:28 PM
    Moderator