====== Active Directory client management, join/unjoin/relocate ou ======
created by LarsG [[lars.gruenheid@civitec.de]] 2015/09/30
* tested under windows 7
* tested under win2012 active directory environment
* tested under winst 4.11.5.14
With this package, you can join or leave a domain, and in theory change the ou-path for the client within a domain (still experimental, details below).
These three functions are conveniently assigned to the action requests setup (join), uninstall (unjoin), update (relocate).
This package relies on three product properties:
* **domain_ou**
''domain_ou'' must follows this syntax: ''[domain.tld][/ou_1/ou_2]'', both segments are independent and optional
and only taken into account when joining a domain or relocating to another ou. when a client shall leave a domain, required information are gathered
from operating system.
if no domain is specified, it's being extracted from host identifier.
if no ou is specified, the client will be placed in the default computer ou-path for the domain.
each ou needs a leading forward-slash, all have to be in the right order, beginning at the top-most level.
* **username**
* **password**
''username'' must include the domain it belongs to, either like ''DOMAIN\username'' or ''username@domain.tld'', and it has to be
an account with sufficient privileges to join/unjoin clients to/from the domain(s) you want to manage.
''username'' and ''password'' are prunned from productproperties upon every successful execution, so that they won't remain for
everyone to see in cleartext. //i hope an option for password-masking in productproperties will be available soon.//
==== Setup ====
if a client currently is in a domain and shall join another, this script will try to unjoin from the current domain,
and then joined to the new domain, with the same administrative account you provided. so you will need one account with sufficient privileges for
both domains, f.e. a trusted management domain containing such administrative accounts. otherwise, you have to do both steps
seperately - first unjoin, then join, with different accounts.
[Actions]
noUpdateScript
defVar $DomainRaw$
defVar $Domain$
defVar $DomainCurrent$
defVar $OUPath$
defVar $OUPathCurrent$
defVar $Username$
defVar $Password$
defVar $ExitCode$
defVar $JoinMode$
defVar $UnJoinMode$
defVar $JOIN_DOMAIN$
defVar $ACCT_CREATE$
defVar $ACCT_DELETE$
defVar $WIN9X_UPGRADE$
defVar $DOMAIN_JOIN_IF_JOINED$
defVar $JOIN_UNSECURE$
defVar $MACHINE_PASSWORD_PASSED$
defVar $DEFERRED_SPN_SET$
defVar $INSTALL_INVOCATION$
;defVar $ACCT_NO_OPTIONS$
;defVar $ACCT_DEACTIVATE$
;calculate joinmode from possible constants
set $JOIN_DOMAIN$ = "1"
set $ACCT_CREATE$ = "2"
set $ACCT_DELETE$ = "4"
set $WIN9X_UPGRADE$ = "16"
set $DOMAIN_JOIN_IF_JOINED$ = "32"
set $JOIN_UNSECURE$ = "64"
set $MACHINE_PASSWORD_PASSED$ = "128"
set $DEFERRED_SPN_SET$ = "256"
set $INSTALL_INVOCATION$ = "262144"
set $JoinMode$ = calculate($JOIN_DOMAIN$+"+"+$ACCT_CREATE$)
;calculate unjoinmode from possible constants
;set $ACCT_NO_OPTIONS$ = "0"
;set $ACCT_DEACTIVATE$ = "2"
;set $UnJoinMode$ = calculate($ACCT_DEACTIVATE$)
;product property domain_ou has the following syntax: [domain.tld][/ou_1/ou_2] - both segments are optional.
;domain must be the fqdn for the domain to join
;ou-path must consist of the hierarchical list of ou's the client should be put in, every ou must have a leading slash.
set $DomainRaw$ = getProductProperty("domain_ou","")
;get domain from product property - if domain is not set, get domain from host identifier.
set $Domain$ = takeString(0, splitString($DomainRaw$,"/"))
if ($Domain$ = "")
set $Domain$ = composeString(getSubList(1 : ,splitString("%HostId%",".")),".")
set $DomainRaw$ = $Domain$ + $DomainRaw$
endif
;get ou-path from product property - if ou-path is not set, join client to standard client-ou-path for the domain.
;ou-path will be transformed into ldap-friendly syntax.
set $OUPath$ = ""
for %OU% in getSubList(1 : ,splitString($DomainRaw$,"/")) do set $OUPath$ = "OU=%OU%,"+$OUPath$
if not ($OUPath$ = "")
for %DC% in splitString($Domain$,".") do set $OUPath$ = $OUPath$+"DC=%DC%,"
set $OUPath$ = strPart($OUPath$,"1",calculate(strLength($OUPath$)+"-1"))
endif
;get username + password from, product properties, hide password value in log
set $Username$ = getProductProperty("username","")
setConfidential getProductProperty("password","")
set $Password$ = getProductProperty("password","")
;check if client is in domain
if ( takeString(0, getOutStreamFromSection('execwith_vbs_check_domain cscript //nologo //e:vbs')) = "0" )
showBitmap "%ScriptPath%\domain.png" "Active Directory"
message "Join domain " + $DomainRaw$
execwith_vbs_domain_join cscript //nologo //e:vbs
sub_check_domain_join
dosinanicon_gpupdate /WaitOnClose
comment "Setting default logon domain, clear previous login user"
registry_default_domain /sysnative
exitwindows /Reboot
else
set $DomainCurrent$ = takeString(0, getOutStreamFromSection('execwith_vbs_get_domain cscript //nologo //e:vbs'))
;check if client is already in domain requested
if not ( $DomainCurrent$ = $Domain$ )
showBitmap "%ScriptPath%\domain.png" "Active Directory"
message "Leave domain " + $DomainCurrent$
execwith_vbs_domain_unjoin cscript //nologo //e:vbs
sub_check_domain_unjoin
;TODO remove client from ad
;TODO check exitcode adsi
exitwindows /ImmediateReboot
else
comment "Client is already in requested domain"
endif
endif
;reset username + password entries in product properties
opsiservicecall_unset_username
opsiservicecall_unset_password
[execwith_vbs_check_domain]
Set obj = GetObject("WinMgmts:\\.\root\cimv2:Win32_ComputerSystem.Name='%PCNAME%'")
Wscript.Echo obj.PartOfDomain
[execwith_vbs_get_domain]
Set obj = GetObject("WinMgmts:\\.\root\cimv2:Win32_ComputerSystem.Name='%PCNAME%'")
Wscript.Echo obj.Domain
[execwith_vbs_domain_join]
Set obj = GetObject("WinMgmts:\\.\root\cimv2:Win32_ComputerSystem.Name='%PCNAME%'")
res = obj.JoinDomainOrWorkGroup("$Domain$", "$Password$", "$Username$", "$OUPath$", $JoinMode$)
Wscript.Quit res
[execwith_vbs_domain_unjoin]
Set obj = GetObject("WinMgmts:\\.\root\cimv2:Win32_ComputerSystem.Name='%PCNAME%'")
res = obj.UnjoinDomainOrWorkgroup("$Password$", "$Username$")
;res = obj.UnjoinDomainOrWorkgroup("$Password$", "$Username$", $UnJoinMode$)
Wscript.Quit res
[dosinanicon_gpupdate]
gpupdate /force
exit %ERRORLEVEL%
[registry_default_domain]
openkey [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon]
set "DefaultDomainName" = REG_SZ:"$Domain$"
set "AltDefaultDomainName" = REG_SZ:"$Domain$"
set "CachePrimaryDomain" = REG_SZ:"$Domain$"
openKey [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI]
set "LastLoggedOnSAMUser" = REG_SZ:""
set "LastLoggedOnUser" = REG_SZ:""
[opsiservicecall_unset_username]
"method": "setProductProperty"
"params": [
"%installingProdName%",
"username",
"",
"%hostid%"
]
[opsiservicecall_unset_password]
"method": "setProductProperty"
"params": [
"%installingProdName%",
"password",
"",
"%hostid%"
]
[sub_check_domain_unjoin]
set $ExitCode$ = getLastExitCode
if $ExitCode$ = "0"
comment "Success"
else
logError "UNKNOWN ERROR " & ReturnValue
isFatalError
endif
[sub_check_domain_join]
set $ExitCode$ = getLastExitCode
if $ExitCode$ = "0"
comment "Success"
else
if $ExitCode$ = "2"
logError "Missing OU"
isFatalError
else
if $ExitCode$ = "5"
logError "Access denied"
isFatalError
else
if $ExitCode$ = "53"
logError "Network path not found"
isFatalError
else
if $ExitCode$ = "87"
logError "Parameter incorrect"
isFatalError
else
if $ExitCode$ = "1326"
logError "Logon failure, user or pass"
isFatalError
else
if $ExitCode$ = "1355"
logError "Domain can not be contacted"
isFatalError
else
if $ExitCode$ = "1909"
logError "User account locked out"
isFatalError
else
if $ExitCode$ = "2224"
logError "Computer Account allready exists"
isFatalError
else
if $ExitCode$ = "2691"
logError "Allready joined"
isFatalError
else
logError "Unknown error " & ReturnValue
isFatalError
endif
endif
endif
endif
endif
endif
endif
endif
endif
endif
==== Uninstall ====
It seems that actually deleting computer accounts from a domain upon unjoin is currently not possible, so keep in mind that you need to manually delete the account if you want it to be gone, f.e. to re-use the name for another computer. //I am planning to add this as an optional feature.//
[Actions]
defVar $DomainCurrent$
defVar $Username$
defVar $Password$
defVar $ExitCode$
defVar $UnJoinMode$
;defVar $ACCT_NO_OPTION$
;defVar $ACCT_DEACTIVATE$
;calculate unjoinmode from possible constants
;set $ACCT_NO_OPTION$ = "0"
;set $ACCT_DEACTIVATE$ = "2"
;set $UnJoinMode$ = calculate($ACCT_DEACTIVATE$)
;get username + password from, product properties, hide password value in log
set $Username$ = getProductProperty("username","")
setConfidential getProductProperty("password","")
set $Password$ = getProductProperty("password","")
;reset username + password entries in product properties
;opsiservicecall_unset_username
;opsiservicecall_unset_password
;check if client is in domain
if not ( takeString(0, getOutStreamFromSection('execwith_vbs_check_domain cscript //nologo //e:vbs')) = "0" )
set $DomainCurrent$ = takeString(0, getOutStreamFromSection('execwith_vbs_get_domain cscript //nologo //e:vbs'))
showBitmap "%ScriptPath%\domain.png" "Active Directory"
message "Leave domain " + $DomainCurrent$
;unjoin domain
execwith_vbs_domain_unjoin cscript //nologo //e:vbs
sub_check_domain_unjoin
;TODO remove client from ad
;TODO check exitcode adsi
;restart client, script will be re-executed upon next opsi request
exitwindows /Reboot
else
comment "Computer is currently not part of a domain"
endif
;reset username + password entries in product properties
opsiservicecall_unset_username
opsiservicecall_unset_password
[execwith_vbs_check_domain]
Set obj = GetObject("WinMgmts:\\.\root\cimv2:Win32_ComputerSystem.Name='%PCNAME%'")
Wscript.Echo obj.PartOfDomain
[execwith_vbs_get_domain]
Set obj = GetObject("WinMgmts:\\.\root\cimv2:Win32_ComputerSystem.Name='%PCNAME%'")
Wscript.Echo obj.Domain
[execwith_vbs_domain_unjoin]
Set obj = GetObject("WinMgmts:\\.\root\cimv2:Win32_ComputerSystem.Name='%PCNAME%'")
res = obj.UnjoinDomainOrWorkgroup("$Password$", "$Username$")
;res = obj.UnjoinDomainOrWorkgroup("$Password$", "$Username$", $UnJoinMode$)
Wscript.Quit res
[opsiservicecall_unset_username]
"method": "setProductProperty"
"params": [
"%installingProdName%",
"username",
"",
"%hostid%"
]
[opsiservicecall_unset_password]
"method": "setProductProperty"
"params": [
"%installingProdName%",
"password",
"",
"%hostid%"
]
[sub_check_domain_unjoin]
set $ExitCode$ = getLastExitCode
if $ExitCode$ = "0"
comment "Success"
else
logError "UNKNOWN ERROR " & ReturnValue
isFatalError
endif
==== Update ====
Relocating a client to a different ou within the same domain is still giving me some headache,
i am currently stuck at the part where the ADSI movehere function actually performs the relocation,
it will throw an error ''Active Directory: not implemented'' (what ever that means).
If anyone can get this to work, i wouldn't mind a heads up (;
[Actions]
defVar $DomainRaw$
defVar $Domain$
defVar $DomainCurrent$
defVar $OUPath$
defVar $OUPathCurrent$
defVar $Username$
defVar $Password$
defVar $ExitCode$
;product property domain_ou has the following syntax: [domain.tld][/ou_1/ou_2] - both segments are optional.
;domain must be the fqdn for the domain to join
;ou-path must consist of the hierarchical list of ou's the client should be put in, every ou must have a leading slash.
set $DomainRaw$ = getProductProperty("domain_ou","")
;get domain from product property - if domain is not set, get domain from host identifier.
set $Domain$ = takeString(0, splitString($DomainRaw$,"/"))
if ($Domain$ = "")
set $Domain$ = composeString(getSubList(1 : ,splitString("%HostId%",".")),".")
set $DomainRaw$ = $Domain$ + $DomainRaw$
endif
;get ou-path from product property - if ou-path is not set, join client to standard client-ou-path for the domain.
;ou-path will be transformed into ldap-friendly syntax.
set $OUPath$ = ""
for %OU% in getSubList(1 : ,splitString($DomainRaw$,"/")) do set $OUPath$ = "OU=%OU%,"+$OUPath$
if not ($OUPath$ = "")
for %DC% in splitString($Domain$,".") do set $OUPath$ = $OUPath$+"DC=%DC%,"
set $OUPath$ = strPart($OUPath$,"1",calculate(strLength($OUPath$)+"-1"))
endif
;get username + password from, product properties, hide password value in log
set $Username$ = getProductProperty("username","")
setConfidential getProductProperty("password","")
set $Password$ = getProductProperty("password","")
if not ( takeString(0, getOutStreamFromSection('execwith_vbs_check_domain cscript //nologo //e:vbs')) = "0" )
set $DomainCurrent$ = takeString(0, getOutStreamFromSection('execwith_vbs_get_domain cscript //nologo //e:vbs'))
;check if client is in domain requested
if ( $DomainCurrent$ = $Domain$ )
set $OUPathCurrent$ = takeString(0, getOutStreamFromSection('execwith_vbs_get_oupath cscript //nologo //e:vbs'))
;check if client is already in ou-path requested
if not ( $OUPathCurrent$ = $OUPath$ )
showBitmap "%ScriptPath%\domain.png" "Active Directory"
message "Relocate to ou-path " + $DomainRaw$
execwith_vbs_move_oupath cscript //nologo //e:vbs
;TODO check exitcode adsi
dosinanicon_gpupdate /WaitOnClose
else
comment "Computer is already in the given ou-path"
endif
else
logError "Computer is not part of the domain requested, moving to another ou-path only works within the same domain"
isFatalError
endif
else
logError "Computer is currently not part of a domain"
isFatalError
endif
[execwith_vbs_check_domain]
Set obj = GetObject("WinMgmts:\\.\root\cimv2:Win32_ComputerSystem.Name='%PCNAME%'")
Wscript.Echo obj.PartOfDomain
[execwith_vbs_get_domain]
Set obj = GetObject("WinMgmts:\\.\root\cimv2:Win32_ComputerSystem.Name='%PCNAME%'")
Wscript.Echo obj.Domain
[execwith_vbs_get_oupath]
Set adoConnection = CreateObject("ADODB.Connection")
adoConnection.Provider = "ADsDSOObject"
adoConnection.Properties("User ID") = "$Username$"
adoConnection.Properties("Password") = "$Password$"
adoConnection.Properties("Encrypt Password") = True
Set adoCommand = CreateObject("ADODB.Command")
adoConnection.Properties("ADSI Flag") = &H200 Or &H1
adoConnection.Open "Active Directory Provider"
Set adoCommand.ActiveConnection = adoConnection
adoCommand.CommandText = "Select distinguishedName from 'LDAP://$Domain$' where objectClass='computer' and cn='%PCNAME%'"
adoCommand.Properties("Searchscope") = 2
Set adoRecordSet = adoCommand.Execute
adoRecordSet.MoveFirst
OUPathCurrent = adoRecordSet.Fields("distinguishedName").Value
Wscript.Echo Mid(OUPathCurrent,InStr(OUPathCurrent,",")+1)
[execwith_vbs_move_oupath]
Set obj = GetObject("LDAP:")
obj.OpenDSObject "LDAP://w8vrsag08.$Domain$/$OUPath$", "$Username$", "$Password$", 1
res = obj.MoveHere("LDAP://CN=%PCNAME%,$OUPathCurrent$", "CN=%PCNAME%")
Wscript.Quit res
[dosinanicon_gpupdate]
gpupdate /force
exit %ERRORLEVEL%
[opsiservicecall_unset_username]
"method": "setProductProperty"
"params": [
"%installingProdName%",
"username",
"",
"%hostid%"
]
[opsiservicecall_unset_password]
"method": "setProductProperty"
"params": [
"%installingProdName%",
"password",
"",
"%hostid%"
]