How to manage local account passwords from Active Directory without LAPS

Introduction

Active Directory provided a mechanism to set account password via GPO preferences. Unfortunately this was done with an encrypted cpassword value for which the decryption key was published and subsequently the functionality was blocked.

Later, Local Administrator Password Solution (LAPS), was adopted by Microsoft and it is well suited for the task.

The approach described in this article is an alternative method to LAPS which can be used if the schema extension requirement of LAPS cannot be done. It also allows for multiple account management.

For this solution you will need:

  • File Share
  • SQL Server
  • Group Policy
  • PasswordImporter
  • PasswordViewer

Both PasswordImport and PasswordViewer is available within the same download here

Create share

Create a folder on a server which will hold the passwords reset output files from computers that get the GPO.

It is important to only allow administrators to view files within this folder. Everyone (authenticated users) should only have create/write permission (as shown below). The reason for this is that password will be stored here in clear-text until the PasswordImporter
tool processed it.

Create GPO

Create a GPO and filter it to the computer objects for the computers you wish to manage passwords for.

 

The startup script needs to be set to save password text files to the share created in an earlier step.

Create SQL Table

Use the following SQL script to create a table within SQL to store passwords. Assign a group to this group to SQL and grant it SELECT rights on the Passwords table. Add all your administrators and support personnel into this group.

CREATE TABLE [dbo].[Passwords](
[ID] [int] IDENTITY(1,1) NOT NULL,
[ComputerName] [varchar](255) NOT NULL,
[Password] [varchar](255) NOT NULL,
CONSTRAINT [PK_Passwords] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

Below is an example of the passwords stored encrypted within SQL

Setup PasswordImporter

PasswordImporter is an application that reads password files from the share and import is, encrypted, into SQL. After a successful import, the password file is deleted.

Open Configurator and specify Location, ConnectionString and LogFile values. Note that Location and ConnectionString needs to be encrypted with encryption key wL!PIQj%EeWj%L^e$nEpdjFzL0d9%Y1#

 

Schedule this application to run every few minutes to import passwords to SQL database

Setup PasswordViewer

PasswordViewer is an application that shows the specified computers password. This application should be distributed to all administrators and support personnel.

Open Configurator and specify FQDN, NetBIOS and ConnectionString values. Note that ConnectionString needs to provide placeholder values for Domain, Username and
Password, for example:

Data source=YOURSQLSERVER;initial catalog=YOURDATABASE;Integrated Security=SSPI; User Id = [DOMAINNAME\USERNAME]; Password = [PASSWORD]

How to thread single-threaded applications

Introduction

CommandThreader is a command line tool that runs commands, line by line, but instead of it running only one at a time, it executes a batch of them so as one closes, it automatically starts a new command. The list of commands is provided via an input file
and the number of simultaneous running commands is specified as a number of allowed threads.

Implementation

Download and extract CommandThreader.zip (VirusTotal Scan Result) to a folder of your choice, on the computer that it will be scheduled to run on.

Demo Execution

In this demo, I execute a dummy application called DummyApp.exe. All this application does is delay for a random number of seconds and as a return code returns this numbers of seconds that it delays. This can
be any application that takes a long time to complete but is not resources intensive.

Input File

C:\Temp\DummyApp\DummyApp.exe|DummyArgument|C:\Temp\DummyApp|false|true|60
C:\Temp\DummyApp\DummyApp.exe|DummyArgument|C:\Temp\DummyApp|false|true|60
C:\Temp\DummyApp\DummyApp.exe|DummyArgument|C:\Temp\DummyApp|false|true|60
C:\Temp\DummyApp\DummyApp.exe|DummyArgument|C:\Temp\DummyApp|false|true|60
C:\Temp\DummyApp\DummyApp.exe|DummyArgument|C:\Temp\DummyApp|false|true|60
C:\Temp\DummyApp\DummyApp.exe|DummyArgument|C:\Temp\DummyApp|false|true|60
C:\Temp\DummyApp\DummyApp.exe|DummyArgument|C:\Temp\DummyApp|false|true|60
C:\Temp\DummyApp\DummyApp.exe|DummyArgument|C:\Temp\DummyApp|false|true|60
C:\Temp\DummyApp\DummyApp.exe|DummyArgument|C:\Temp\DummyApp|false|true|60
C:\Temp\DummyApp\DummyApp.exe|DummyArgument|C:\Temp\DummyApp|false|true|60
C:\Temp\DummyApp\DummyApp.exe|DummyArgument|C:\Temp\DummyApp|false|true|60
C:\Temp\DummyApp\DummyApp.exe|DummyArgument|C:\Temp\DummyApp|false|true|60
C:\Temp\DummyApp\DummyApp.exe|DummyArgument|C:\Temp\DummyApp|false|true|60
C:\Temp\DummyApp\DummyApp.exe|DummyArgument|C:\Temp\DummyApp|false|true|60
...

 

Input File breakdown

C:\Temp\DummyApp\DummyApp.exe|DummyArgument|C:\Temp\DummyApp|
false|true|
60

  • Command to run
  • Arguments for the command
  • Working folder
  • Execute command hidden
  • Wait for command to exit
  • Maximum amount of time that CommandThreader should wait before killing process

CommandThreader Command

With the command below a total of 5 threads will be maintained

CommandThreader.exe DummyInput.txt 5

 

CommandThreader Output

Conclusion

With CommandThreader, the time taken to run 100s long running processes can be drastically reduced.

Non-standard success exit codes

Some software distribution tools do not allow custom success exit codes. This means that if a software package returns a non-standard success exit code that the deployment status might return a failure instead of a success.

With this script an installation package’s non-standard success exit codes can be translated to exit code 0 within the software distribution tool.

Example: CScript.exe Setup.vbs /Command:SomeSetup.exe /SuccessCodes:1;2;3
In this example exit code 1,2 and 3 for SomeSetup.exe will be changed to 0

Attachment(s): [list-attachments]

[sourcecode language=”vb”]
Option Explicit

On Error Resume Next

Dim strCommand
Dim strSuccessCodes

strCommand = WScript.Arguments.Named("Command")
strSuccessCodes = WScript.Arguments.Named("SuccessCodes")

If Len(Trim(strCommand)) = 0 Or Len(Trim(strSuccessCodes)) = 0 Then
WScript.Quit(1)
End If

Dim arrSuccessCodes
Dim intSuccessCode

arrSuccessCodes = Split(strSuccessCodes,";")

Dim objShell
Dim objExec
Dim intReturnCode

Set objShell = CreateObject("WScript.Shell")

Err.Clear
Set objExec = objShell.Exec(strCommand)
If Err.Number <> 0 Then
WScript.Echo "Problem with command"
WScript.Quit(1)
End If

Do While objExec.Status = 0
Call WScript.Sleep(100)
Loop

intReturnCode = objExec.ExitCode

For Each intSuccessCode In arrSuccessCodes
If IsNumeric(intSuccessCode) Then
If intReturnCode = CInt(intSuccessCode) Then
WScript.Echo "Success"
intReturnCode = 0
Exit For
End If
End If
Next

Set objExec = Nothing
Set objShell = Nothing

Call WScript.Quit(intReturnCode)
[/sourcecode]

Excel Macro to Convert to CamelHumpNotation

This is a macro that convert values to CamelHumpNotation
[sourcecode language=”vb”]
Sub FixCamelHump()

‘ FixCamelHump Macro

Dim strValue As String

Range("A1").Select
strValue = ActiveCell.Value
Do While strValue <> ""
ActiveCell.Value = FixThisString(strValue)
ActiveCell.Offset(1, 0).Select
strValue = ActiveCell.Value
Loop
End Sub

Private Function FixThisString(strValue As String)
Dim intCnt As Integer
Dim blnStartOfWord As Boolean
Dim strBuild As String
Dim strCurrentChar As String

For intCnt = 1 To Len(strValue)
strCurrentChar = Mid(strValue, intCnt, 1)
If IsLetter(strCurrentChar) = True Then
If blnStartOfWord = False Then
blnStartOfWord = True
strBuild = strBuild & UCase(strCurrentChar)
Else
strBuild = strBuild & LCase(strCurrentChar)
End If
Else
If blnStartOfWord = True Then
blnStartOfWord = False
End If
strBuild = strBuild & strCurrentChar
End If
Next
FixThisString = strBuild
End Function

Private Function IsLetter(strChar As String) As Boolean
If Asc(strChar) >= 97 And Asc(strChar) <= 122 Or Asc(strChar) >= 65 And Asc(strChar) <= 90 Then
IsLetter = True
Exit Function
End If
IsLetter = False
End Function
[/sourcecode]

Recreate DNS Hosts From Export File Into a Microsoft DNS Server

Recreate DNS Hosts From Export File Into a Microsoft DNS Server

The following script was created to add hosts, from a list of exported hosts and IPs originally from a unrelated DNS server’s zone, into the specified Microsoft DNS server’s DNS zone.

The basic usage of the script is:
CScript AddDNSHost.vbs /DNSServer:DNSServer /DNSZone:DNSZone /HostName:HostName /HostIP:HostIP
And an example:
AddDNSHost.vbs /DNSServer:192.168.0.1 /DNSZone:DNSZone.local /HostName:MyComputer /HostIP:192.168.1.123

A very simple way of constructing multiple commands can be achieved with practically any speadsheet application where column A holds the list of host names, column B holds their respected IP addresses and column C the following command (starting from row 1):

=”AddDNSHost.vbs /DNSServer:192.168.0.1 /DNSZone:DNSZone.local /HostName:” & A1 & ” /HostIP:” & B1
The above command can be copied once for each row.

The resulting constructed command can then be directly pasted into a command prompt

[sourcecode language=”vb”]
On Error Resume Next

strDNSServer = Wscript.Arguments.Named("DNSServer")
strDNSZone = Wscript.Arguments.Named("DNSZone")
strHostName = Wscript.Arguments.Named("HostName")
strHostIP = Wscript.Arguments.Named("HostIP")

If Len(Trim(strDNSServer)) &gt; 0 And Len(Trim(strDNSZone)) &gt; 0 And Len(Trim(strHostName)) &gt; 0 And Len(Trim(strHostIP)) &gt; 0 Then
If Right(UCase(strHostName), Len(strDNSZone) + 1) "." &amp; UCase(strDNSZone) Then
strHostName = strHostName &amp; "." &amp; strDNSZone
End If
intRecordClass = 1
intTTL = 600

strComputer = "."
Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\" &amp; strComputer &amp; "rootMicrosoftDNS")
Set objItem = objWMIService.Get("MicrosoftDNS_AType")
intReturn = objItem.CreateInstanceFromPropertyData(strDNSServer, strDNSZone, strHostName, intRecordClass, intTTL, strHostIP)
If Err.Number = 0 And intReturn = 0 Then
WScript.Echo strHostName &amp; vbTab &amp; "Added"
Else
WScript.Echo strHostName &amp; vbTab &amp; "Failed"
End If
End If
[/sourcecode]

Enumerate All Active Directory Users’ ProxyAddresses

Enumerate All Active Directory Users’ ProxyAddresses

The following script was created to enumerate all the various addresses an Active Directory users might have.

This output of the script can be piped to a text file which in turn can be imported into a database to generate various reports.

Basic steps are
1) Create connection to Active Directory domain
2) Create recordset from query, filtering in only user accounts
3) Enumerate through recordset, display combination of sAMAccountName and proxyAddress
4) Cleanup

[sourcecode language=”vb”]
Option Explicit

On Error Resume Next

Dim objCommand
Dim objConnection
Dim objRootDSE
Dim strDNSDomain
Dim strBase
Dim objSystemInfo
Dim strDomain
Dim strFilter
Dim strAttributes
Dim strQuery
Dim objRecordset
Dim strUserName
Dim strCN
Dim objUser
Dim arrproxyAddresses
Dim strproxyAddresses

Set objCommand = CreateObject("ADODB.Command")
Set objConnection = CreateObject("ADODB.Connection")

objConnection.Provider = "ADsDSOObject"
objConnection.Open "Active Directory Provider"
objCommand.ActiveConnection = objConnection

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

strDNSDomain = objRootDSE.Get("defaultNamingContext")
strBase = ""

Set objSystemInfo = CreateObject("ADSystemInfo")
strDomain = objSystemInfo.DomainShortName

strFilter = "(&amp;(objectCategory=Person)(objectClass=User))"

strAttributes = "sAMAccountName,cn,proxyAddresses"

strQuery = strBase &amp; ";" &amp; strFilter &amp; ";" &amp; strAttributes &amp; ";subtree"
objCommand.CommandText = strQuery
objCommand.Properties("Page Size") = 100
objCommand.Properties("Timeout") = 30
objCommand.Properties("Cache Results") = False

Set objRecordset = objCommand.Execute

Do Until objRecordset.EOF
strUserName = objRecordset.Fields("sAMAccountName").Value
strCN = objRecordset.Fields("cn").value
arrproxyAddresses = objRecordset.Fields("proxyAddresses").value
If IsArray(arrproxyAddresses) = True Then
For Each strproxyAddresses In arrproxyAddresses
WScript.Echo strUserName &amp; vbTab &amp; strproxyAddresses
Next
End If
objRecordset.MoveNext
Loop

objRecordset.Close
objConnection.Close

set objUser = Nothing
Set objRecordset = Nothing
Set objSystemInfo = Nothing
Set objRootDSE = Nothing
Set objConnection = Nothing
Set objCommand = Nothing
[/sourcecode]

Attachment(s):
[list-attachments]