Jump to content

One image to rule them all..


Recommended Posts

I just thought I would document what I have done for a "one image to rule them all" build here - in case the information is useful to other people..

Outline of what this does:

1) Basic installation on any hardware platform (only NIC+Mass storage are included)

2) Detect machine type (desktop/laptop) and model (bios model)

3) "Fix" computer accounts in A/D (explained later on..)

4) Join domain, download drivers for model and install

5) Install software (configurable per machine)

6) Copies user profiles (if found for that computer)

7) Updates via WSUS/Windows update etc..

Why in gods name does it do this "stuff"?!

I inherited this network in a rather sad state:

* No standard hardware (a quick scan of the network yeilds ~60 different configurations)

* No standard naming convention (Bob1, Warehouse2, Lenovo-123456)

* Slow (aging hardware, domain migration, patch bloat..)

* Software on many machines is non-standard (eg. alot of the workstations have one-off pieces of software installed)

Now for the fun part..

<Image Path>\i386\Templates\image.sif


[Unattended]
UnattendMode=FullUnattended
OemPreinstall=Yes
TargetPath=\WINDOWS
NtUpgrade=No
OverwriteOemFilesOnUpgrade=No
OemSkipEula=Yes
DUDisable=Yes
DriverSigningPolicy=Ignore
ExtendOemPartition=1
ForceHALDetection=Yes
Repartition=Yes
OemSkipWelcome=1
UnattendSwitch=Yes
OemPnPDriversPath = Drivers\Nic
[GuiUnattended]
AdminPassword=<password here>
EncryptedAdminPassword=Yes
OEMSkipRegional=1
TimeZone=290
OemSkipWelcome=1
[Identification]
JoinWorkgroup=RIS_BUILD
[GuiRunOnce]
Command0=%SYSTEMDRIVE%\INSTALL\post_install.cmd

Only relavent code is included for brevity.. so a very basic sif.. JoinWorkgroup is used because I don't want to expose passwords in the sif..

<Image Path>\$OEM$\cmdlines.txt


[COMMANDS]
"create_installer_account.cmd"

<Image Path>\$OEM$\create_installer_account.cmd


net user Installer installer /add
net localgroup Administrators Installer /add
net accounts /maxpwage:unlimited
REGEDIT /S installer_autologon.reg
EXIT

<Image Path>\$OEM$\installer_autologon.reg


Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon]
"DefaultUserName"="Installer"
"DefaultPassword"="installer"
"AutoAdminLogon"="1"

Right, so during the GUI setup, create a local admin account called "Installer", password "installer", then patch the registry so it will log in automatically.. easy!

Now here's where things get tricky..

<Image Path>\$OEM$\$1\INSTALL\post_install.cmd


@echo off
SETLOCAL ENABLEDELAYEDEXPANSION

REM Some configuration..

SET RIS_SERVER=<WDS SERVER>
SET FILE_SERVER=<FILE SERVER>


REM ensure we are in the correct folder.. not really needed..
%SYSTEMDRIVE%
CD \INSTALL
SET INSTALLDIR=%SYSTEMDRIVE%\INSTALL

REM now where were we...
REM dir command used to check if we have a state, create one if not..
DIR "%INSTALLDIR%\STATE.CMD" >NUL 2>NUL|| CALL :SET_STATE FIRST_BOOT

REM load known settings
DIR "%INSTALLDIR%\SETTINGS.CMD" >NUL 2>NUL && CALL "%INSTALLDIR%\SETTINGS.CMD"

CALL "%INSTALLDIR%\STATE.CMD"

IF "%INSTALLSTATE%"=="FIRST_BOOT" (
ECHO Starting Setup..
ECHO ****************
) ELSE (
ECHO Resuming Setup..
ECHO ****************
)

REM go to where we left off..
GOTO %INSTALLSTATE%

REM #######################################
:FIRST_BOOT

SET WSUS_ITERATION=0
CALL :SET_SETTING WSUS_ITERATION %WSUS_ITERATION%

CALL :CRUDE_WAIT_FOR_NETWORK
REM We aren't on the domain yet, so use a VERY limited local account on RIS server
REM to retrieve information we need..
NET USE \\%RIS_SERVER%\COMPUTER_CONFIG /USER:%RIS_SERVER%\Ris_Installer R1s1nstaller /PERSISTENT:NO
REM determine what type of PC we are on..
SET PLATFORM=
FOR /F %%I IN ('CSCRIPT //NoLogo get_computer_type.vbs') DO SET PLATFORM=%%I
REM Default to Laptop if not sure.. can't hurt too much!
IF "%PLATFORM%"=="Other" SET PLATFORM=Laptop

REM see if there is a configuration for this MAC address..
SET MACADDRESS=
FOR /F %%I IN ('CSCRIPT //NoLogo determine_used_mac_address.vbs') DO SET MACADDRESS=%%I
REM default to "default" configuration
IF "%MACADDRESS%"=="" (
SET MACADDRESS=%PLATFORM%
SET NAME=%COMPUTERNAME%
) ELSE (
FOR /F %%I IN ('TYPE \\%RIS_SERVER%\COMPUTER_CONFIG\%MACADDRESS%\name.txt') DO SET NAME=%%I
)

REM Get list of software to install..

SET SOFTWARE=
FOR /F %%i IN ('TYPE \\%RIS_SERVER%\COMPUTER_CONFIG\%MACADDRESS%\software.txt') DO SET SOFTWARE=!SOFTWARE! %%i

REM Set up a "bootstrap" for subsequent boots..
REM Use "5" so model specific scripts, app install etc.. can sort before us, if they want..
echo @CALL %INSTALLDIR%\post_install.cmd>"%ALLUSERSPROFILE%\Start Menu\Programs\Startup\5.cmd"

REM Save for later use..
CALL :SET_SETTING PLATFORM %PLATFORM%
CALL :SET_SETTING MACADDRESS %MACADDRESS%
CALL :SET_SETTING NAME %NAME%
CALL :SET_SETTING SOFTWARE %SOFTWARE%

ECHO Detected Platform: %PLATFORM%
ECHO Using Configuration: %MACADDRESS%
ECHO Computer Name: %NAME%
ECHO Packages to install: %SOFTWARE%

REM "Prompter.hta" attempts to:
REM Join the domain
REM Change computer name
REM Record Credentials in registry for autologon
REM Reboot

REM If for whatever reason it fails to perform the domain functions
REM It will inform the user of the failure, and request that they
REM Manually join/rename the PC, then reboot

REM Arguments are: [<CompterName> <MacAddress>] [<Username> <Password>]
REM eg. IT001 ab-cd-ef-12-34 Administrator MyPassword
CALL :SET_STATE CHECK_DOMAIN
mshta %INSTALLDIR%\Prompter.hta %NAME% %MACADDRESS%

CALL :REBOOT

GOTO :EOF
REM #######################################
:CHECK_DOMAIN

CALL :CRUDE_WAIT_FOR_NETWORK

REM Crude.. but effective..
ECHO Checking domain..
DIR \\<Domain Controller>\netlogon 2>NUL >NUL || GOTO :NOT_JOINED

CALL :SET_STATE INSTALL_DRIVERS
GOTO :INSTALL_DRIVERS

:NOT_JOINED
ECHO "ERROR: Computer is not joined to the domain"
ECHO " Please resolve and press a key to continue"
PAUSE
CALL :REBOOT

GOTO :EOF
REM #######################################
:INSTALL_DRIVERS

CALL :WAIT_FOR_NETWORK

REM first, get drivers for current platform (or FAILSAFE)
ECHO Getting drivers from %RIS_SERVER%..
CSCRIPT //NoLogo "%INSTALLDIR%\get_model_drivers.vbs"

REM Install the drivers using DPInst..
ECHO Installing Drivers..
PUSHD %SYSTEMDRIVE%\Drivers
DPINST.EXE /c /s /sh /lm /el /sa
POPD

REM run post-install script
ECHO Running driver post-install script..
DIR "%INSTALLDIR%\driver_post_install.cmd" 2>NUL >NUL && CALL "%INSTALLDIR%\driver_post_install.cmd"

CALL :SET_STATE SET_RESOLUTION
CALL :REBOOT

GOTO :EOF
REM #######################################
:SET_RESOLUTION

REM Change resolution - some app installers require a minimum res..
ECHO Setting resolution..
ECHO Trying 1024x768/auto
"%INSTALLDIR%\QRes.exe" /x:1024 /y:768 > NUL 2>NUL
ECHO Trying 1024x768/24
"%INSTALLDIR%\QRes.exe" /x:1024 /y:768 /c:24 > NUL 2>NUL
ECHO Trying 1024x768/32
"%INSTALLDIR%\QRes.exe" /x:1024 /y:768 /c:32 > NUL 2>NUL

CALL :SET_STATE INSTALL_SOFTWARE
GOTO INSTALL_SOFTWARE

GOTO :EOF
REM #######################################
:INSTALL_SOFTWARE

CALL :WAIT_FOR_NETWORK
ECHO Installing Software..
CSCRIPT //NoLogo \\%FILE_SERVER%\applications\installers\install.js %SOFTWARE%
CSCRIPT //NoLogo \\%FILE_SERVER%\applications\installers\check_install.js %SOFTWARE% || GOTO SOFTWARE_NOT_FINISHED

CALL :SET_STATE COPY_USER_PROFILES
GOTO COPY_USER_PROFILES

:SOFTWARE_NOT_FINISHED
ECHO Not all software installed correctly, rebooting for retry..
CALL :REBOOT

GOTO :EOF
REM #######################################
:COPY_USER_PROFILES

CALL :WAIT_FOR_NETWORK
ECHO Copying user profiles..
FOR %%i in (\\%RIS_SERVER%\COMPUTER_CONFIG\%MACADDRESS%\profiles\*.*) DO CALL "%%i"

CALL :SET_STATE WSUS_UPDATES
GOTO WSUS_UPDATES

GOTO :EOF
REM #######################################
:WSUS_UPDATES

CALL :WAIT_FOR_NETWORK
REM use 3 iterations for WSUS..
IF %WSUS_ITERATION% GEQ 3 GOTO WSUS_FINISHED

SET /A WSUS_ITERATION=0%WSUS_ITERATION%+1 >NUL 2>NUL
CALL :SET_SETTING WSUS_ITERATION %WSUS_ITERATION%

CSCRIPT //NoLogo "%INSTALLDIR%\wsus_update.vbs" action:install mode:silent restart:1 force:1
CALL :REBOOT
GOTO :EOF

:WSUS_FINISHED

CALL :SET_STATE CLEANUP
GOTO CLEANUP

GOTO :EOF
REM #######################################
:CLEANUP

NET USER Installer /delete
REG delete "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v DefaultPassword /f
REG delete "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v AutoAdminLogon /f
DEL "%ALLUSERSPROFILE%\Start Menu\Programs\Startup\5.cmd"
%SYSTEMDRIVE%
CD \
RMDIR /s /q %INSTALLDIR% && shutdown -r -t 0

GOTO :EOF
REM #######################################
:SET_STATE
SETLOCAL
SET STATE=%1
ECHO SET "INSTALLSTATE=%STATE%"> %INSTALLDIR%\STATE.CMD
ENDLOCAL
GOTO :EOF

:SET_SETTING
SETLOCAL
SET SETTING=%1
SET VALUE=%2
REM just append it to the end of the file.. even if there are duplicate entries, the last
REM set value will always overwrite, so it's OK..
ECHO SET "%SETTING%=%VALUE%">> %INSTALLDIR%\SETTINGS.CMD
ENDLOCAL
GOTO :EOF

:REBOOT
SETLOCAL
ECHO Rebooting..
shutdown -r -t 0
ENDLOCAL
GOTO :EOF

:WAIT_FOR_NETWORK
SETLOCAL
ECHO Waiting for network..
:WAIT_FOR_NETWORK_LOOP
DIR \\<Domain Controller>\netlogon >NUL 2>NUL || GOTO WAIT_FOR_NETWORK_LOOP
ENDLOCAL
GOTO :EOF

:CRUDE_WAIT_FOR_NETWORK
SETLOCAL
ECHO Waiting for network..
:CRUDE_WAIT_FOR_NETWORK_LOOP
PING 192.168.45.210 -n 1 2>NUL| find "Reply" | find "time" >NUL || GOTO CRUDE_WAIT_FOR_NETWORK_LOOP
ENDLOCAL
GOTO :EOF

This nasty looking batch file is basically a state machine, it calls various scripts, reboots.. and continues where it left off..

Initally the batch file sets up some basic configuration, loads previous settings (STATE.CMD and SETTINGS.CMD), then jumps to it's current "state"

FIRST_BOOT

Get the computer type via get_computer_type.vbs (returns either Laptop or Desktop)

Get the mac address (OK.. this is a little bit of a lie.. determine_used_mac_address.vbs checks if there is a folder with the computers mac address, if present it returns the mac address.. otherwise nothing)

Default mac address to computer type (explained later..)

If a valid mac address is found.. get further configuration from a folder on the WDS server (name, software to install)

Create a cmd file in the all users startup folder, for subsequent reboots..

Show the "prompter", which requests a username/password/computer name, "fixes" computer accounts, saves the username/password in the registry for autologon, and reboots..

CHECK_DOMAIN

Try and connect to the domain controllers netlogon share, post a message if it fails..

INSTALL_DRIVERS

Calls the get_mode_drivers.vbs script, which determines the computer model, then looks on the WDS server for drivers for that model, then copies them locally. This script has a "failsafe", which copies everything from driverpacks.net..

Use DPINST.EXE to install PnP drivers

Note: some hardware configurations don't have drivers that install cleanly (eg. nvidia graphics cards), so the driver_post_install.cmd can be used to run driver executables to "fix" things like that..

SET_RESOLUTION

Some software we use will not install if the resolution/colour depth are set too a low level..

Uses QRes to (try) change the resolution

INSTALL_SOFTWARE

Ug.. not gonna go into any detail on my software installation scripts here (happy to answer questions though..), needless to say, I have written a set of scripts that behave somewhat similar to apt-get/yum etc.. (eg. software is installed as a "package", packages can have dependcies etc..)

COPY_USER_PROFILES

Does what it says, profile backup script included below to show how it works..

WSUS_UPDATES

calls the wsus_update.vbs script, then reboots.. 3 times for sanity

CLEANUP

Remove autologon information, remove the script "bootstrapper" from the all users profile, then self destruct :)

Phew! hope that high level description is enough for you guys.. feel free to ask questions..

Right.. now onto the scripts themselves.. (in order of execution)

<Image Path>\$OEM$\$1\INSTALL\get_computer_type.vbs


strComputer = "."
Set objWMIService = GetObject("winmgmts:" _
& "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
Set colChassis = objWMIService.ExecQuery _
("Select * from Win32_SystemEnclosure")

dim strType

For Each objChassis in colChassis
For Each strChassisType in objChassis.ChassisTypes
Select Case strChassisType
Case 1
strType = "Other"
Case 2
strType = "Other"
Case 3
strType = "Desktop"
Case 4
strType = "Desktop"
Case 5
' strType = "Other"
Case 6
strType = "Desktop"
Case 7
strType = "Desktop"
Case 8
strType = "Laptop"
Case 9
strType = "Laptop"
Case 10
strType = "Laptop"
Case 11
strType = "Laptop"
Case 12
' strType = "Other"
Case 13
' strType = "Other"
Case 14
strType = "Laptop"
Case 15
' strType = "Other"
Case 16
' strType = "Other"
Case 17
strType = "Desktop"
Case 18
' strType = "Other"
Case 19
' strType = "Other"
Case 20
' strType = "Other"
Case 21
' strType = "Other"
Case 22
' strType = "Other"
Case 23
strType = "Desktop"
Case 24
strType = "Desktop"
Case Else
strType = "Unknown"
End Select
Next
Next

WScript.echo strType

Fairly straight forward.. look at the ChassisType(s) for the computer, then spit it out to stdout..

<Image Path>\$OEM$\$1\INSTALL\determine_used_mac_address.vbs


strComputer = "."
set objFSO = Wscript.CreateObject("Scripting.FileSystemObject")
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colItems = objWMIService.ExecQuery _
("Select * From Win32_NetworkAdapterConfiguration Where IPEnabled = True")

For Each objItem in colItems
strMac = Replace(objItem.MACAddress,":","-")
if objFSO.FolderExists("\\<WDS SERVER>\COMPUTER_CONFIG\" & strMac) then
Wscript.Echo strMac
Wscript.quit
end if
Next

Iterate through each MAC address the computer has, check if \\<WDS SERVER>\COMPUTER_CONFIG\<Mac Address> exists and spit it to stdout, otherwise nothing..

<Image Path>\$OEM$\$1\INSTALL\Prompter.hta


<html>
<head>
<HTA:APPLICATION
ID="objInstaller"
APPLICATIONNAME="Installer"
SCROLL="no"
SINGLEINSTANCE="yes"
WINDOWSTATE="normal"
BORDER="dialog"
CAPTION="Installer"
>
<title>Installer</title>
<body>
<script language="VBScript">

Const JOIN_DOMAIN = 1
Const ACCT_CREATE = 2
Const ACCT_DELETE = 4
Const WIN9X_UPGRADE = 16
Const DOMAIN_JOIN_IF_JOINED = 32
Const JOIN_UNSECURE = 64
Const MACHINE_PASSWORD_PASSED = 128
Const DEFERRED_SPN_SET = 256
Const INSTALL_INVOCATION = 262144

private function readRegString(strKeyPath,strValueName)
const HKEY_LOCAL_MACHINE = &H80000002
strComputer = "."

Set oReg=GetObject("winmgmts:{impersonationLevel=impersonate}!\\" &_
strComputer & "\root\default:StdRegProv")

oReg.GetStringValue HKEY_LOCAL_MACHINE,strKeyPath,strValueName,strValue

readRegString = strValue

end function

private sub WriteRegistry(strPath,strValue)
Set objShell = CreateObject("WScript.Shell")
objShell.RegWrite strPath, strValue, "REG_SZ"
end sub

private function dostuff()

strComputerName = document.getelementbyid("computername").value
strUserName = document.getelementbyid("username").value
strUserPassword = document.getelementbyid("password").value

if not fix_computer_account(strUserName,strUserPassword,strComputerName,strOldComputerName,strMacAddress) then
exit function
end if

WriteRegistry "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\DefaultUserName", "<DOMAIN>\" & strUserName
WriteRegistry "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\DefaultDomainName", "<DOMAIN>"
WriteRegistry "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\DefaultPassword", strUserPassword
WriteRegistry "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\AutoAdminLogon", "1"

if not join_domain_and_rename(strUserName,strUserPassword,strComputerName) then
Msgbox "An error occured during domain configuration, you can try again with a different username/password" & vbcrlf & _
"or close this window and attempt to fix it yourself"
exit function
end if

Set arrOS = GetObject("winmgmts:{(Shutdown)}//./root/cimv2").ExecQuery _
("select * from Win32_OperatingSystem where Primary=true")

for each objOS in arrOS
objOS.Reboot()
next

self.close

end function

function fix_computer_account(strUser,strPassword,strNewComputerName,strOldComputerName,strMacAddress)

Set objShell = CreateObject("WScript.Shell")

strCmd1 = objShell.ExpandEnvironmentStrings("%INSTALLDIR%") & "\RunasPro.exe " & _
strUser & "@DOMAIN.COM " & strPassword & " c:\"

strCmd2 = """cscript " + objShell.ExpandEnvironmentStrings("%INSTALLDIR%")+"\fix_compter_account.vbs"
strCmd2 = strCmd2 & " " & strOldComputerName & " " & strNewComputerName & " " & strMacAddress & """"

objShell.Run strCmd1 & " " & strCmd2, 0, True

fix_computer_account = true

end function

function join_domain_and_rename(strUser,strPassword,strNewCompterName)

'Right, here's where things get.. interesting
'WScript doesn't appear to support binding to LDAP using alternate
'Credentials, unless you can bind to it via NTLM first! (unless..
'presumably.. you mess with the premissions in AD.. let's not..
'So.. what the hell do we do?!
'We CAN connect to another PC's WMI service, supplying credentials..
'So.. we proxy the compter account "fudging" through it! what a task!

join_domain_and_rename = True

strDomain = "DOMAIN.COM"

Set objNetwork = CreateObject("WScript.Network")
strComputer = objNetwork.ComputerName

Set objWMIService = GetObject ("winmgmts:" & "!\\" & strComputer & "\root\cimv2")

Set objComputer = GetObject("winmgmts:{impersonationLevel=Impersonate}!\\" & _
strComputer & "\root\cimv2:Win32_ComputerSystem.Name='" & _
strComputer & "'")

ReturnValue = objComputer.JoinDomainOrWorkGroup(strDomain, strPassword, strDomain & "\" & strUser, "OU=Workstations,DC=DOMAIN,dc=COM", _
JOIN_DOMAIN)

if ReturnValue <> 0 then
ReturnValue = objComputer.JoinDomainOrWorkGroup(strDomain, strPassword, strDomain & "\" & strUser, "OU=Workstations,DC=DOMAIN,dc=COM", _
JOIN_DOMAIN + ACCT_CREATE)
end if

If ReturnValue <> 0 Then
MsgBox "Computer not added to domain successfully. Return value: " & ReturnValue
join_domain_and_rename = false
exit function
End If

if lcase(strNewCompterName) <> lcase(strCompter) then

strComputer = "."
Set objWMIService = GetObject("winmgmts:" _
& "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
Set colComputers = objWMIService.ExecQuery _
("Select * from Win32_ComputerSystem")

For Each objComputer in colComputers
ErrCode = objComputer.Rename(strNewCompterName, strPassword, strUser)
If ErrCode <> 0 Then
MsgBox "Eror changing computer name. Error code: " & ErrCode
join_domain_and_rename = false
exit function
End If
Next
end if
end function

Dim strComputerName,strUserName,strUserPassword
Dim strOldComputerName, strNewComputerName, strMacAddress

strOldComputerName = readRegString("SYSTEM\CurrentControlSet\Control\ComputerName\ActiveComputerName","ComputerName")

arrCommands = Split(objInstaller.commandLine, " ")

if UBound(arrCommands) > 1 then
strNewComputerName = arrCommands(1)
strMacAddress = arrCommands(2)
else
strNewComputerName = strOldComputerName
strMacAddress = "FFAAFFAAFFAAFFAAFFAA" 'Dummy mac.. shouldn't cause trouble unless there
'is a computer account in AD called "ITFFAAFFAAFFAAFFAAFFAA"..
end if

if UBound(arrCommands) = 4 then
strUserName = arrCommands(3)
strUserPassword = arrCommands(4)
else
strUserName = "Administrator"
strUserPassword = ""
end if

Dim s
s = "<tr><td>Computer Name</td>"

s = s + "<td><input id=""computername"" type=""text"" value=""" & strNewComputerName & """></td></tr>"
s = s + "<tr><td>Username</td><td><input id=""username"" type=""text"" value=""" & strUserName & """></td></tr>"
s = s + "<tr><td>Password</td><td><input id=""password"" type=""password"" value="""&strUserPassword&"""></td></tr>"

document.write "<table>"
document.write s
document.write "</table>"

if Len(strUserPassword) > 0 then
dostuff
end if
</script>
<input type="button" name="go" value="Install" onclick="dostuff()">
</body>
</html>

Display HTML page with three inputs (computername, username, password), When "Install" is clicked, do the following:

1) Put the username/password into registry for autologon

2) Attempt to "fix" computer accounts in AD (explained below)

3) Attempt to join the domain

4) Attempt to rename the computer account

Now.. 1, 3 and 4 are easy! 2.. not so easy.. The problem is, the script must run under a domain account in order for it to connect to AD, and fix "stuff.."

As the computer is not yet a member of the domain (and this "fixing" needs to happen prior to it joining the domain..), I created a program called "RunasPro",

Which is similar to "RunAs", except you can supply a password as a parameter, and it does not do any authentication (eg. you can run xyz.exe as UNTRUSTEDDOMAIN\UnknownUser happily..)

RunasPro.cpp


#define UNICODE
#define _WIN32_WINNT 0x0500

#include <windows.h>
#include <stdio.h>
#include <userenv.h>

void DisplayError(LPWSTR pszAPI)
{
LPVOID lpvMessageBuffer;

FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM,
NULL, GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPWSTR)&lpvMessageBuffer, 0, NULL);

//
//... now display this string
//
wprintf(L"ERROR: API = %s.\n", pszAPI);
wprintf(L" error code = %d.\n", GetLastError());
wprintf(L" message = %s.\n", (LPWSTR)lpvMessageBuffer);

//
// Free the buffer allocated by the system
//
LocalFree(lpvMessageBuffer);

ExitProcess(GetLastError());
}


int main(int argc, WCHAR *argv[])
{
HANDLE hToken;
PROCESS_INFORMATION pi = {0};
STARTUPINFO si = {0};

WCHAR szUserName[1024];
WCHAR szPassw0rd[1024];
WCHAR szCommand[1024];
WCHAR szWorkingDirectory[1024];

si.cb = sizeof(STARTUPINFO);

if (argc != 5)
{
printf("Usage: %s [user@domain] [password] [working directory] [cmd] ", (char*)argv[0]);
printf("\n\n");
return 1;
}

MultiByteToWideChar(CP_ACP,MB_ERR_INVALID_CHARS,(char*)argv[1],-1,szUserName,sizeof(szUserName)/sizeof(szUserName[0]));
MultiByteToWideChar(CP_ACP,MB_ERR_INVALID_CHARS,(char*)argv[2],-1,szPassw0rd,sizeof(szPassw0rd)/sizeof(szPassw0rd[0]));
MultiByteToWideChar(CP_ACP,MB_ERR_INVALID_CHARS,(char*)argv[3],-1,szWorkingDirectory,sizeof(szWorkingDirectory)/sizeof(szWorkingDirectory[0]));
MultiByteToWideChar(CP_ACP,MB_ERR_INVALID_CHARS,(char*)argv[4],-1,szCommand,sizeof(szCommand)/sizeof(szCommand[0]));

if (!CreateProcessWithLogonW(szUserName, NULL, szPassw0rd,
LOGON_NETCREDENTIALS_ONLY, NULL, szCommand,
0, NULL, szWorkingDirectory,
&si, π))
DisplayError(L"CreateProcessWithLogonW");

WaitForSingleObject(pi.hProcess,INFINITE);

CloseHandle(hToken);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}

Very straight forward.. the only important part is the CreateProcessWithLogonW part, which, when called with LOGON_NETCREDENTIALS_ONLY, allows running under any account..

<Image Path>\$OEM$\$1\INSTALL\fix_computer_account.vbs


dim strOldComperName, objOldComputer
dim strNewCompterName, objNewComputer
dim strMacAddress, objMacAddress

strOldComputerName = WScript.Arguments(0)
strNewCompterName = WScript.Arguments(1)
strMacAddress = WScript.Arguments(2)

WScript.echo strOldComputerName
WScript.echo strNewCompterName
WScript.echo strMacAddress

strMacAddress = "IT"&Replace(strMacAddress,"-","")



set objOldComputer = FindComputer(strOldComputerName&"$")
set objNewComputer = FindComputer(strNewCompterName&"$")
set objMacAddress = FindComputer(strMacAddress&"$")



if not objOldComputer is nothing then
if not objNewComputer is nothing then
if objOldComputer.adsPath <> objNewComputer.adsPath then
MergeGroupMembership objNewComputer, objOldComputer
DeleteComputer objNewComputer
end if
end if
if not objMacAddress is nothing then
if objOldComputer.adsPath <> objMacAddress.adsPath then
MergeGroupMembership objMacAddress, objOldComputer
DeleteComputer objMacAddress
end if
end if
ResetPassword objOldComputer
elseif not objNewComputer is nothing then
if not objMacAddress is nothing then
if objNewComputer.adsPath <> objMacAddress.adsPath then
MergeGroupMembership objMacAddress, objNewComputer
DeleteComputer objMacAddress
end if
end if
set objNewComputer = RenameComputer(objNewComputer, strOldComputerName)
ResetPassword objNewComputer
elseif not objMacAddress is nothing then
Set objMacAddress = RenameComputer(objMacAddress, strOldComputerName)
ResetPassword objMacAddress
end if

Sub MergeGroupMembership(objComputerSource,objComputerDest)
WScript.Echo "Merging: " & objComputerSource.cn & " -> " & objComputerDest.cn
On Error Resume Next
dim arrGroupList : arrGroupList = objComputerSource.GetEx("memberOf")
For Each strGroupDN in arrGroupList
Set objGroup = GetObject("LDAP://" & strGroupDN)
objGroup.add objComputerDest.adsPath
next
On Error GoTo 0

end Sub

function FindComputer(strAccountName)

'Set objRootDSE = GetObject("LDAP://RootDSE")
'strNC = objRootDSE.Get("defaultNamingContext")
strNC = "<DomainController>.DOMAIN.COM"

Set objConn = CreateObject("ADODB.Connection")
objConn.Provider = "ADSDSOObject"
objConn.Open "ADs Provider"

strQuery = "(&(objectClass=computer)(samAccountName="&strAccountName&"))"

strLdapQuery = "<LDAP://" & strNC & ">;" & strQuery & ";adspath;subtree"

Set objRS = objConn.Execute(strLdapQuery)

if Not objRS.EOF then
Set objRes = GetObject (objRS.Fields(0).Value)
set FindComputer = GetObject(objRes.adspath)
exit function
end if

set FindComputer = Nothing

end function

Sub ResetPassword(objComputer)
objComputer.SetPassword objComputer.samAccountName
end sub

Sub DeleteComputer(objComputer)
objComputer.DeleteObject (0)
End sub

Function RenameComputer(objComputer,strNewName)

dim strOU :strOU = Mid(objComputer.distinguishedName,InStr(objComputer.distinguishedName,",")+1)
set objOU = GetObject("LDAP://" & strOU)

WScript.Echo "Moving " & objComputer.adsPath & " -> " & strNewname &" : " & strOU
Set RenameComputer = objOU.MoveHere(objComputer.adsPath,"CN="&strNewName)


RenameComputer.Put "sAMAccountName", strNewName & "$"
RenameComputer.Put "displayName", strNewName & "$"
RenameComputer.Put "dNSHostName", strNewName & ".DOMAIN.COM"

RenameComputer.SetInfo

end Function

OK uhh.. there MAY be three computer accounts associated with this computer (don't ask..), IT<Mac Address>, BOB1/WAREHOUSE1/etc, and IT#### (new naming convention)

Now, each of these computer accounts may have been in security groups etc for GPOs

oldComputerName is the CURRENT computer name (eg. the name the computer will join the domain as)

newComputerName is the name which we will be renamed to..

macAddress is.. obvious..

So.. this script adds the OLD COMPUTER to all groups which macAddress and newComputer belong to, deletes macAddress and newComputer, and resets the password on oldComputer

Or.. similar.. eg. if oldComputer doesn't exist, it will merge new+mac, rename to old, then reset.. etc..

<Image Path>\$OEM$\$1\INSTALLget_model_drivers.vbs


dim strModel
Set objShell = CreateObject("WScript.Shell")
Set objWshScriptExec = objShell.Exec("systeminfo")
Set objStdOut = objWshScriptExec.StdOut

While Not objStdOut.AtEndOfStream
strLine = objStdOut.ReadLine
If InStr(strLine,"System Model") Then
strModel = trim(split(strLine,":")(1))
End If
Wend

dim objFSO,objFolder
set objFSO = Wscript.CreateObject("Scripting.FileSystemObject")
set objFolder = objFSO.GetFolder("\\<WDS SERVER>\MODEL_SPECIFIC")

strModelDirectory = "FAILSAFE"

for each subfolder in objFolder.subFolders
dim compareLength
compareLength = len(strModel)
if len(subfolder.name) < compareLength then
compareLength = len(subfolder.name)
end if
if left(lcase(subfolder.name),compareLength) = left(lcase(strModel),compareLength) then
strModelDirectory = subfolder.name
end if
next

objShell.Run "%COMSPEC% /c echo d | xcopy /E ""\\<WDS SERVER>\MODEL_SPECIFIC\" & strModelDirectory & "\Drivers"" ""%SYSTEMDRIVE%\Drivers""",,true
objShell.Run "%COMSPEC% /c copy /y ""\\<WDS SERVER>\MODEL_SPECIFIC\" & strModelDirectory & "\post_install.bat"" """ & objShell.ExpandEnvironmentStrings("%INSTALLDIR%") & "\driver_post_install.cmd""",,true

OK err.. run systeminfo, read the output and parse the "System Model", then, using that model, look on the WDS folder as follows:

eg. Computer Model: ABC123

Search order:

..\A

..\AB

..\ABC

..etc

In other words, match as close as possible to the model name, this way, computers with different revisions (ABCv1, ABCv2, ABCv3) can be grouped into ABC!

wsus_update.vbs is not included here - it was poached from:

http://www.vbshf.com/?p=13

That's all for now guys - feel free to ask any questions you like!

Link to comment
Share on other sites


I think some of your procedures are overcomplicated, here are some of the thing I do to solve the same problems.

On my network I created an AD account that can join a computer to the domain but can't do much else. I don't care what model I'm installing to because I have INF files for every device in my organization (some of them were extremely difficult to extract from packaged installers). For user profiles I use roaming profiles. I set the policy for WSUS from cmdlines.txt.

Link to comment
Share on other sites

  • 2 weeks later...

Hi Bezalel,

I would agree that the procedure is more complicated than need be there are a couple of reasons for this though:

1) Credentials are not embedded in the build because we have a policy.. no domain credentials are to be stored in plain text.. anywhere.. (I added provision for the script to have credentials embedded in it however, and this has been used when rebuilding large numbers of machines outside of hours.. kinda bending policy here..)

2) Management doesn't want to fork out for storage for roaming profiles.. (C'mon.. What the hell?!!!)

3) The script is designed to request credentials/computer name by default (machines need to be labeled/tagged at build/rebuild time, so can't really be automated in a sane fashion..)

4) Drivers.. blah.. I had 3 days to knock the whole thing together.. I would have liked to build a database for PCI ID's/drivers.. but 3 days wasn't gonna cut it!

5) WSUS.. it is handled by a GPO, but do you really want to leave it up to users to keep installing updates for the next few days? :) The script just forces installation of ALL updates on the spot

Aside from that - I think what I've put together _should_ be easier to maintain (eg. adding drivers) for whoever replaces me.. this place is run on a shoe-string, I doubt they will get someone who has the know-how to build up driver inf's for.. interesting hardware.. Whereas this process will allow them to basically extract drivers from *.zip/*.exe etc.. dump them in a folder and give it a whirl.. if something doesn't install on it's own, add the driver installer executable to the respective post_install batch file with a silent switch.. and be done with it..

Oh, on another note - do you get much trouble with multiple drivers for the same PCI ID? or are you fixing inf's by hand on conflict?

Cheers,

Mike

Link to comment
Share on other sites

Oh, on another note - do you get much trouble with multiple drivers for the same PCI ID? or are you fixing inf's by hand on conflict?

When I have multiple drivers with the same ID I let Windows determine which is the best driver to install. I don't touch the INF's in order not to invalidate the WHQL certificates.

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...