Doug's AD Blog

  • Home
  • About
  • Active Directory
  • Scripting
  • Powershell
  • DirectAccess

Custom Local Administrator Password

September 6, 2009 by Doug

One of the challenges that we have in our organization is dealing with local administrator passwords.  Through the years, we have used various models for our local admin password to various affect.  One of the biggest issues we have run across, though, is that typically we have had one password applied across all of our workstations in our enterprise.  We all know that is bad, but finding an alternative can be difficult.  At one point, we ran a script that would generate a unique password for the local admin account.  Unfortunately, this password had to be written to a single file and was a pain to use as it used any keyboard characters (determining what some of those characters were was difficult).

Over the years, I have also read various advice.  Jesper Johansson (a now former MS employee) recommends disabling the local administrator account (http://technet.microsoft.com/en-us/magazine/2006.01.securitywatch.aspx ).  His theory is that if you get to a point where everything is broken and you need the local admin account, rebuild the machine.  It sounds OK in theory, but I don’t know that we are willing to take that step.  Perhaps if we had our SCCM environment all setup so that reimaging a workstation was easy and we knew that all important local data was backed up, it might be a different story. 

Instead, we have opted for a different method.  We are running a script against workstations to give each workstation a unique custom local admin password.  (Our service desk techs all have their own workstation admin account giving them local admin rights on workstations with a named account.)  In this posting, I will set out the method that we use to generate this unique custom password. 

First, let me make a note of what I mean when I say unique custom  password.  We use a tool from Jesper Johansson called passgen.  We can then feed into this tool “seeds”.  In this case, one of the seeds is the workstation name and the second is a passphrase.  The tool then takes these two parts and creates a unique password.  (In our case, we have also set the length of the password and limited it to using alpha characters, capital and lowercase, and numbers.)  Because this tool generates the password based upon known input, we can easily recreate what the local admin password is on a given machine.  (Note that less than a handful of people know what the passphrase seed is, so we use scripts for the techs to run if they need to find the local admin password. In fact, we have this script integrated into a custom Active Directory Users and Computers mmc snap-in that we publish for each tech.  They are able to click on the workstation name and it will give them an option to generate the local admin password.)  Here is the passgen command we run passgen -g ComputerName  Phrase -l 10 -e 2 -c 500 –h.

One of the other features we wanted to include in the script is the ability to change the custom password if we needed to give it out to the end user.  We have done this by using group membership.  Initially, all workstations are not in a group.  Because of this, the script sets variables for what the passgen command will be.  If we need to iterate the password to the next level, we can add the workstation to a group named WKS_Local Administrator Password x (where x is a number 1-9).  This changes the workstation name in the passgen command to have an _X (again where X is the matching number 1-9).  Doing this creates an entirely new unique password for that workstation using the new seed.  (For example, a workstation may generate the following password when in none of the groups:  YOJ9N0NWvZ .  When we add it to group 1 (and so the workstation name becomes WKS12345_1 , the new generated password becomes 26lcaK9DWO .)  This gives us the ability to iterate the password up to 10 times for each workstation. 

I also have the script set so that it will only run once.  It does this by creating a file on the local machine.  If that file exists, it does not run.  I primarily do this for performance reasons, but want to do some further testing to see if this is necessary.  I actually think it would be better to continuously run this script at every startup so that if it gets changed, we are changing it back to what it should be.  (Eventually, if I do continue to set it to run only once, I would prefer to use a registry key to do so.  I am still working on learning the best and most reliable method for that.)

So, now let’s get to the point and show you the script.

   1: '========================================================================== 

   2: ' 

   3: ' NAME: ChangePasswordLocalAdmin.vbs 

   4: ' 

   5: 'VERSION:  1.2.0 

   6: 'MODIFIED:  Doug Neely 

   7: 'DATE:  02/27/09 

   8: '       This will change the password of the local admin account so that every 

   9: '        computer is different, but can be easily retrieved using passgen. 

  10: ' 

  11: 'Ideally, I am wanting this to be able to check group membership.  This will 

  12: '    be an easy way for us to increment the password changes using groups. 

  13: '    Group membership will change the variables of the computer name. 

  14: 'This starts by going through and checking if C:\CONTOSO exists.  If not, it 

  15: '    creates it.  Next, it looks to see if the strSuccess exists.  If it does, 

  16: '    it ends.  If it doesn't, it runs the passgen command.  If it is successful, 

  17: '    it creates a success file and if it fails it creates a failure file. 

  18: ' 

  19: 'MODIFIED:  Doug Neely 

  20: 'DATE:       06/08/09 

  21: '            Revised this script to use a different method for checking group 

  22: '            membership.  This method will place all groups into a dictionary 

  23: '            item for later checking.  It can handle nested groups, primary group 

  24: '            and cross-domain group membership.  This has been pulled from 

  25: '            Robert Mueller's site at http://www.rlmueller.net/IsMember6.htm 

  26: ' 

  27: 'Additional Notes: 

  28: 'passgen -g strComputer strPhrase -l 10 -e 2 -c 500 -h 

  29: '    -g generates a password using the computer name and phrase as a seed 

  30: '    -l 10 - length of 10 characters 

  31: '    -e 2 - limits the password characters to upper case, lower case and numbers 

  32: '    -c 500 - changes the password of the "500" account - administrator 

  33: '    -h  - output is hidden 

  34: '========================================================================== 

  35: Option Explicit 

  36: ' Declare objects and variables with global scope. 

  37: Dim objGroupList, adoCommand, adoConnection, objRootDSE 

  38: Dim adoRecordset, strAttributes, strFilter, strQuery 

  39: Dim oADSysInfo, objFSO, objShell, strCONTOSO 

  40: Dim sComputerName, strpassgen, strPhrase, strKeyPath, strValueName, strValue 

  41: Dim    strComputerPath, objComputer, strGroup, shellcmd, strSuccess, sCMD, ND, objFile 

  42: '========================================================================== 

  43: 'On Error Resume Next 

  44: Set oADSysInfo     = CreateObject("ADSystemInfo") 

  45: Set objFSO = CreateObject ("Scripting.FileSystemObject") 

  46: Set objShell = CreateObject("WScript.Shell") 

  47: '========================================================================== 

  48: 'Configuration Block 

  49: strCONTOSO = "c:\CONTOSO\" 

  50: sComputerName     = GetComputerName 

  51: strpassgen="\\contoso.com\netlogon\passgen.exe" 

  52: strPhrase="HereIsWhereYouWouldPlaceYourUniquePassPhrase!" 

  53: '========================================================================== 

  54:  

  55: strComputerPath = "LDAP://" & oADSysInfo.ComputerName 

  56: Set objComputer = GetObject(strComputerPath) 

  57:  

  58: 'Here this looks for group membership. 

  59:  

  60: strGroup = "WKS_Local Administrator Password 1" 

  61: If (IsMember(objComputer, strGroup) = True) Then 

  62:         shellcmd="\\contoso.com\netlogon\passgen -g " & sComputerName & "_1 " & strPhrase & " -l 10 -e 2 -c 500 -h" 

  63:         strValue = "1" 

  64:         strSuccess="C:\CONTOSO\ads1.admp" 

  65:     Else 

  66:  

  67: End If 

  68:  

  69: strGroup = "WKS_Local Administrator Password 2" 

  70: If (IsMember(objComputer, strGroup) = True) Then 

  71:         shellcmd="\\contoso.com\netlogon\passgen -g " & sComputerName & "_2 " & strPhrase & " -l 10 -e 2 -c 500 -h" 

  72:         strValue = "2" 

  73:         strSuccess="C:\CONTOSO\ads2.admp" 

  74:     Else 

  75:  

  76: End If 

  77:  

  78: strGroup = "WKS_Local Administrator Password 3" 

  79: If (IsMember(objComputer, strGroup) = True) Then 

  80:         shellcmd="\\contoso.com\netlogon\passgen -g " & sComputerName & "_3 " & strPhrase & " -l 10 -e 2 -c 500 -h" 

  81:         strValue = "3" 

  82:         strSuccess="C:\CONTOSO\ads3.admp" 

  83:     Else 

  84:  

  85: End If 

  86:  

  87: strGroup = "WKS_Local Administrator Password 4" 

  88: If (IsMember(objComputer, strGroup) = True) Then 

  89:         shellcmd="\\contoso.com\netlogon\passgen -g " & sComputerName & "_4 " & strPhrase & " -l 10 -e 2 -c 500 -h" 

  90:         strValue = "4" 

  91:         strSuccess="C:\CONTOSO\ads4.admp" 

  92:     Else 

  93:  

  94: End If 

  95:  

  96: strGroup = "WKS_Local Administrator Password 5" 

  97: If (IsMember(objComputer, strGroup) = True) Then 

  98:         shellcmd="\\contoso.com\netlogon\passgen -g " & sComputerName & "_5 " & strPhrase & " -l 10 -e 2 -c 500 -h" 

  99:         strValue = "5" 

 100:         strSuccess="C:\CONTOSO\ads5.admp" 

 101:     Else 

 102:  

 103: End If 

 104:  

 105: strGroup = "WKS_Local Administrator Password 6" 

 106: If (IsMember(objComputer, strGroup) = True) Then 

 107:         shellcmd="\\contoso.com\netlogon\passgen -g " & sComputerName & "_6 " & strPhrase & " -l 10 -e 2 -c 500 -h" 

 108:         strValue = "6" 

 109:         strSuccess="C:\CONTOSO\ads6.admp" 

 110:     Else 

 111:  

 112: End If 

 113:  

 114: strGroup = "WKS_Local Administrator Password 7" 

 115: If (IsMember(objComputer, strGroup) = True) Then 

 116:         shellcmd="\\contoso.com\netlogon\passgen -g " & sComputerName & "_7 " & strPhrase & " -l 10 -e 2 -c 500 -h" 

 117:         strValue = "7" 

 118:         strSuccess="C:\CONTOSO\ads7.admp" 

 119:     Else 

 120:  

 121: End If 

 122:  

 123: strGroup = "WKS_Local Administrator Password 8" 

 124: If (IsMember(objComputer, strGroup) = True) Then 

 125:         shellcmd="\\contoso.com\netlogon\passgen -g " & sComputerName & "_8 " & strPhrase & " -l 10 -e 2 -c 500 -h" 

 126:         strValue = "8" 

 127:         strSuccess="C:\CONTOSO\ads8.admp" 

 128:     Else 

 129:  

 130: End If 

 131:  

 132: strGroup = "WKS_Local Administrator Password 9" 

 133: If (IsMember(objComputer, strGroup) = True) Then 

 134:         shellcmd="\\contoso.com\netlogon\passgen -g " & sComputerName & "_9 " & strPhrase & " -l 10 -e 2 -c 500 -h" 

 135:         strValue = "9" 

 136:         strSuccess="C:\CONTOSO\ads9.admp" 

 137:     Else 

 138:  

 139: End If 

 140:  

 141:     If shellcmd = "" Then 

 142:         shellcmd="\\contoso.com\netlogon\passgen -g " & sComputerName & " " & strPhrase & " -l 10 -e 2 -c 500 -h" 

 143:         strValue = "0" 

 144:         strSuccess="C:\CONTOSO\ads.admp" 

 145:         Else 

 146:     End If 

 147:  

 148: If NOT objFSO.FileExists(strSuccess) Then 

 149:     'Deletes any previous success/failure files 

 150:     Set objShell = CreateObject("WScript.Shell") 

 151:     sCMD = "%COMSPEC% /c del C:\CONTOSO\*.admp" 

 152:     objShell.Run sCMD, 0, True 

 153:     'Set objShell = nothing 

 154:  

 155:     ND = objShell.Run(Shellcmd,,True) 

 156:         'Finally, we will create a success file.  We will inventory this file with SCCM so we can verify success. 

 157:         Set objFile = objFSO.CreateTextFile(strSuccess, False) 

 158:         objFile.Close 

 159:  

 160: Else 

 161:     'We do nothing and end 

 162: End If 

 163:  

 164: ' Clean up. 

 165: adoConnection.Close 

 166: Set objGroupList = Nothing 

 167: Set objRootDSE = Nothing 

 168: Set adoCommand = Nothing 

 169: Set adoConnection = Nothing 

 170: Set adoRecordset = Nothing 

 171:  

 172: Function GetComputerName() 

 173:     Dim WshNetwork 

 174:     Set WshNetwork = WScript.CreateObject("WScript.Network") 

 175:    ' default value 

 176:     GetComputerName = "." 

 177:     GetComputerName = WshNetwork.ComputerName 

 178: End Function 

 179:  

 180: 'The Function IsMember and the Sub LoadGroups are a more robust way to check for group membership 

 181: 'I have pulled these functions from http://www.rlmueller.net/Programs/IsMember6.txt 

 182: Function IsMember(ByVal objADObject, ByVal strGroup) 

 183:     ' Function to test for group membership. 

 184:     ' objADObject is a user or computer object. 

 185:     ' strGroup is the NT name (sAMAccountName) of the group to test. 

 186:     ' objGroupList is a dictionary object, with global scope. 

 187:     ' ADO is used to retrieve all group objects from the domain, with 

 188:     ' their PrimaryGroupToken. Each objADObject has a PrimaryGroupID. 

 189:     ' The group with the matching PrimaryGroupToken is the primary group. 

 190:     ' Returns True if the user or computer is a member of the group. 

 191:     ' Subroutine LoadGroups is called once for each different objADObject. 

 192:  

 193:     Dim strPrimaryGroup, strDNSDomain 

 194:     Dim intPrimaryGroupToken, intPrimaryGroupID 

 195:  

 196:     If (IsEmpty(objGroupList) = True) Then 

 197:        ' Create dictionary object. 

 198:        Set objGroupList = CreateObject("Scripting.Dictionary") 

 199:         objGroupList.CompareMode = vbTextCompare 

 200:  

 201:         ' Use ADO to retrieve all group "primaryGroupToken" values. 

 202:         Set adoConnection = CreateObject("ADODB.Connection") 

 203:         Set adoCommand = CreateObject("ADODB.Command") 

 204:         adoConnection.Provider = "ADsDSOObject" 

 205:         adoConnection.Open "Active Directory Provider" 

 206:         Set adoCommand.ActiveConnection = adoConnection 

 207:         adoCommand.Properties("Page Size") = 100 

 208:         adoCommand.Properties("Timeout") = 30 

 209:         adoCommand.Properties("Cache Results") = False 

 210:         strAttributes = "sAMAccountName,primaryGroupToken" 

 211:         Set objRootDSE = GetObject("LDAP://RootDSE") 

 212:         strDNSDomain = objRootDSE.Get("defaultNamingContext") 

 213:         strFilter = "(objectCategory=group)" 

 214:         strQuery = "<LDAP://" & strDNSDomain & ">;" & strFilter & ";" _ 

 215:             & strAttributes & ";subtree" 

 216:         adoCommand.CommandText = strQuery 

 217:         Set adoRecordset = adoCommand.Execute 

 218:     End If 

 219:     If (objGroupList.Exists(objADObject.sAMAccountName & "\") = False) Then 

 220:        ' Call LoadGroups for each different objADObject. 

 221:         ' Add object name to dictionary object so groups need only be 

 222:         ' enumerated once. 

 223:        Call LoadGroups(objADObject, objADObject) 

 224:         objGroupList.Add objADObject.sAMAccountName & "\", True 

 225:  

 226:         ' Determine which group is the primary group for this object. 

 227:        intPrimaryGroupID = objADObject.primaryGroupID 

 228:         adoRecordset.MoveFirst 

 229:         Do Until adoRecordset.EOF 

 230:             intPrimaryGroupToken = adoRecordset.Fields("primaryGroupToken").Value 

 231:             If (intPrimaryGroupToken = intPrimaryGroupID) Then 

 232:                 strPrimaryGroup = adoRecordset.Fields("sAMAccountName").Value 

 233:                 objGroupList.Add objADObject.sAMAccountName & "\" _ 

 234:                     & strPrimaryGroup, True 

 235:                 Exit Do 

 236:             End If 

 237:             adoRecordset.MoveNext 

 238:         Loop 

 239:         adoRecordset.Close 

 240:     End If 

 241:  

 242:    ' Check group membership. 

 243:     IsMember = objGroupList.Exists(objADObject.sAMAccountName & "\" _ 

 244:         & strGroup) 

 245: End Function 

 246:  

 247: Sub LoadGroups(ByVal objPriADObject, ByVal objSubADObject) 

 248:     ' Recursive subroutine to populate dictionary object with group 

 249:     ' memberships. When this subroutine is first called by Function 

 250:     ' IsMember, both objPriADObject and objSubADObject are the user or 

 251:     ' computer object. On recursive calls objPriADObject still refers to the 

 252:     ' user or computer object being tested, but objSubADObject will be a 

 253:     ' group object. The dictionary object objGroupList keeps track of group 

 254:     ' memberships for each user or computer separately. 

 255:     ' For each group in the MemberOf collection, first check to see if 

 256:     ' the group is already in the dictionary object. If it is not, add the 

 257:     ' group to the dictionary object and recursively call this subroutine 

 258:     ' again to enumerate any groups the group might be a member of (nested 

 259:     ' groups). It is necessary to first check if the group is already in the 

 260:     ' dictionary object to prevent an infinite loop if the group nesting is 

 261:     ' "circular". The MemberOf collection does not include any "primary" 

 262:     ' groups. 

 263:  

 264:    Dim colstrGroups, objGroup, j 

 265:     colstrGroups = objSubADObject.memberOf 

 266:     If (IsEmpty(colstrGroups) = True) Then 

 267:         Exit Sub 

 268:     End If 

 269:     If (TypeName(colstrGroups) = "String") Then 

 270:        ' Escape any forward slash characters, "/", with the backslash 

 271:         ' escape character. All other characters that should be escaped are. 

 272:         colstrGroups = Replace(colstrGroups, "/", "\/") 

 273:         Set objGroup = GetObject("LDAP://" & colstrGroups) 

 274:         If (objGroupList.Exists(objPriADObject.sAMAccountName & "\" _ 

 275:                 & objGroup.sAMAccountName) = False) Then 

 276:             objGroupList.Add objPriADObject.sAMAccountName & "\" _ 

 277:                 & objGroup.sAMAccountName, True 

 278:             Call LoadGroups(objPriADObject, objGroup) 

 279:         End If 

 280:         Set objGroup = Nothing 

 281:         Exit Sub 

 282:     End If 

 283:     For j = 0 To UBound(colstrGroups) 

 284:         ' Escape any forward slash characters, "/", with the backslash 

 285:         ' escape character. All other characters that should be escaped are. 

 286:         colstrGroups(j) = Replace(colstrGroups(j), "/", "\/") 

 287:         Set objGroup = GetObject("LDAP://" & colstrGroups(j)) 

 288:         If (objGroupList.Exists(objPriADObject.sAMAccountName & "\" _ 

 289:                 & objGroup.sAMAccountName) = False) Then 

 290:             objGroupList.Add objPriADObject.sAMAccountName & "\" _ 

 291:                 & objGroup.sAMAccountName, True 

 292:             Call LoadGroups(objPriADObject, objGroup) 

 293:         End If 

 294:     Next 

 295:     Set objGroup = Nothing 

 296: End Sub 

Posted in: Uncategorized Tagged: Active Directory, Scripts, Workstations

Tags

Active Directory DirectAccess Domino Group Policy IBM Notes IPv6 O365 Password PowerShell Scripting Scripts TDI Workstations

Site Admin

  • Log in
  • Entries feed
  • Comments feed
  • WordPress.org

Copyright © [the-year] [site-link].

Powered by [wp-link] and [theme-link].