Wednesday, December 9, 2009

PXEFilter

Pimp my PXE-boot screen

It was “a few” years ago since I did use a HEX-editor… but with a little time over last night I did a small hack to the PXE-boot files.

Why I did it…
1. I wanted to test if I still know how to use a HEX-editor
2. I don’t like the original text.
Do I need to mention: This is NOT supported by anyone and if you break any deployment-solution… don’t blame me.
First, make a backup (*doh*).
The files you are looking for are located on your PXE-point, X:\RemoteInstall\SMSBoot\x86 (and \x64)
I use XVI32 v2.51 www.chmaas.handshake.de/delphi/freeware/xvi32/xvi32.htm to make the changes in the binary files.
Open up PXEBOOT.COM in XVI and search for “Press F12″ (case sensitive).
Then replace the text. If you have a shorter text, fill it out with blank spaces. If you have a longer text… tough luck. :-\
"Press F12 for network service boot"
"Press F12 for snowland deployment "
 
The original text:
hexedit-press-f12
And the changed text:
hexedit-press-f12-snowland
Save and test to PXE-boot a machine.
(If you can’t see the new text, you probably need to copy the changed file to both the x86 and the x64 directory)
That wasn’t to hard, was it?
If it was… don’t bother to try the WDSNBP.COM-file. :-P
The largest textblock that a user can see is the following text, this is what we want to change / pimp.
The details below show the information relating to the PXE boot request for
This computer. Please provide these details to your Windows Deployment Services
Administrator so that this request can be approved.
         1         2         3         4         5         6         7         8
12345678901234567890123456789012345678901234567890123456789012345678901234567890
 
The easy way is just to change the text to something else, but why not use some ascii-art?
If you want to use ascii-art, the maximum width is 51 chars (the width of the third line) and 3 lines high since you need to replace and not add/delete anything.
Of course the first and second line can be a bit longer.
So… start up some texteditor with monospace font (notepad will do) and create some ascii-art.
   __  _  _  __  _    _ _    __  _  _ __
  (__  |\ | |  | |    | |   |__| |\ | |  \  Deployment Services is loading…
  .__) | \| |__|  \/\/  |__ |  | | \| |__/
         1         2         3         4         5         6         7         8
12345678901234567890123456789012345678901234567890123456789012345678901234567890
 
Now that we have some ascii-art with loads of blank spaces we need to replace the right number of spaces with something that we can see.
###__##_##_##__##_####_#_####__##_##_#__###################################
##(__##|\#|#|##|#|####|#|###|__|#|\#|#|##\##Deployment#Services#is#loading…##
##.__)#|#\|#|__|##\/\/##|__#|##|#|#\|#|__/#########
         1         2         3         4         5         6         7         8
12345678901234567890123456789012345678901234567890123456789012345678901234567890
 
The number of # should be exactly the same as the original text.
Start up XVI32 and load WDSNBP.COM, search for “The details below”… now comes the boring/tricky part. Replace the original text with your new ascii-art.
I did a small “translation”-file where I have the orignal row and the new row next to each other, like this for the second row.
This computer. Please provide these defails to your Windows Deployment Services
##(__##|\#|#|##|#|####|#|###|__|#|\#|#|##\##Deployment#Services#is#loading…##
 
Now you can see that the text “computer.” sould be replaced to ” |\ | | “. Yes, it will be hard to read and easy to do wrong.
There is a search/replace option in XVI32, if you use it remember: replace with the exact same number of characters.
Save, PXE-boot and enjoy… :-)
The original:
f12-orginal
The final result:
f12-snowland
The MD5-hash of the WDSNBP.COM and PXEBOOT.COM-files in both x86 and x64 directories is the same so you only have to make the changes in one of them.
wdsnbp-md5-hash

Read PXEFilter settings from registry

First off… populate HKEY_LOCAL_MACHINE\SOFTWARE\snowland\PXEFilter\ with some strings (ProviderServer, SiteCode, Username, Password, Collection).
Second, edit the pxefilter.vbs and change the following lines:
1sProviderServer = ""
2sSiteCode = "ABC"
3sNamespace = "root\sms\site_" & sSiteCode
4sUsername = ""
5sPassword = ""
6sCollection = "ABC00004"   'This must be a collection ID, not a collection name
To this new code:
01Function readRegistry(sRegKey, sDefaultValue)
02    Dim oWshShell, sVal
03    Set oWshShell = CreateObject("WScript.Shell")
04    On Error Resume Next
05    sVal = oWshShell.RegRead(sRegKey)
06    If Err.Number <> 0 Then
07        readRegistry = sDefaultValue
08    Else
09        readRegistry = sVal
10    End If
11    On Error Goto 0
12    Set oWshShell = Nothing
13End Function
14 
15Const cKeyPath = "HKEY_LOCAL_MACHINE\SOFTWARE\snowland\PXEFilter\"
16sProviderServer = readRegistry(cKeyPath & "ProviderServer""")
17sSiteCode = readRegistry(cKeyPath & "SiteCode",              "ABC")
18sUsername = readRegistry(cKeyPath & "Username",               "")
19sPassword = readRegistry(cKeyPath & "Password",               "")
20sCollection = readRegistry(cKeyPath & "Collection",           "ABC00004")
21sNamespace = "root\sms\site_" & sSiteCode
Now, on startup the filter will read from HKLM to get the settings, if there is an error when reading (no value, no rights) the script will default to “ABC” for sSiteCode and so on…

PXE Filter and Obsolete Machines

There is a small problem with the PXE filter and obsolete machines.
Today the PXE-Filter scans the SCCM-database for the machine and doesn’t bother if the machine is obsolete or not… and that might result in problems (The filter adds the obsolete machine to your collection).
Since the PXE-Filter is a VBScript… you can create a small hack…
Around row 150 you will find:
1bFound = False
2Set oClients = oSMS.ExecQuery(sQuery)
3For each oClient in oClients
4    bFound = true
5    iResourceID = oClient.ResourceID
6    PXE.LogTrace "Found existing machine " & oClient.NetbiosName & " with ResourceID = " & iResourceID
7    sNetBiosName = oClient.NetbiosName
8    Exit For
9Next
Change that piece of code to:
01bFound = False
02Set oClients = oSMS.ExecQuery(sQuery)
03For each oClient in oClients
04    If oClient.Obsolete = 1 Then
05        PXE.LogTrace "Found obsolete machine " & oClient.NetbiosName & ", skipping"
06    Else
07        bFound = True
08        iResourceID = oClient.ResourceID
09        PXE.LogTrace "Found existing machine " & oClient.NetbiosName & " with ResourceID = " & iResourceID
10        sNetBiosName = oClient.NetbiosName
11        Exit For
12    End If
13Next
That will result In a WDSServer.log (If you turn on tracing)
[4332] 09:49:22: [WDSPXE] [Microsoft.BDD.PXEFilter] Request from 10.26.64.240:67 Len:548
[4332] 09:49:22: [WDSPXE] [Microsoft.BDD.PXEFilter] About to run script
[4332] 09:49:22: [WDSPXE] [Microsoft.BDD.PXEFilter] Processing request from MAC address = 00:1E:0B:34:0F:E7, IP address = 0.0.0.0, UUID = E11832C1-58C0-DC11-BBDA-0B340FE7001E
[4332] 09:49:22: [WDSPXE] [Microsoft.BDD.PXEFilter] Found obsolete machine RIRO003, skipping
[4332] 09:49:22: [WDSPXE] [Microsoft.BDD.PXEFilter] Found existing machine RIRO003 with ResourceID = 112
[4332] 09:49:22: [WDSPXE] [Microsoft.BDD.PXEFilter] Added new membership rule to collection B0100012
 

PXE Filter (v4.1.501)

Got a comment on another post from Thomas who was asking for a version of the PXE Filter that works.
I have used this one on a few installations.
You need to configure:
sProviderServer if you have the PXE point on another server
sSiteCode for your site
sCollection to the collection which you advertise your deployment task sequence
What the PXEFilter does is (in general):
  • Check if the machine exist, and if not adds it to the SCCM.
  • Checks if the machine is member of a specific collection (sCollection) and if not adds it to the collection (Direct member)
  • Waits until the machine can see the advertisment (A maximum of 30 seconds)
PXEFilter.vbs v4.1.501 (Download VBS)
001' //***************************************************************************
002' // ***** Script Header *****
003' //
004' // Solution:  Microsoft Deployment Toolkit
005' // File:      PXEFilter.vbs
006' //
007' // Purpose:   Decide what needs to be done for each PXE requests received
008' //            by WDS.  If not present in the ConfigMgr database, add it.
009' //
010' // Usage:     (loaded automatically by Microsoft.BDD.PXEFilter DLL)
011' //
012' // Microsoft Solution Version:  4.1.501
013' // Microsoft Script Version:    4.1.501
014' // Customer Build Version:      1.0.0
015' // Customer Script Version:     1.0.0
016' //
017' // Microsoft History:
018' // 4.0.501 MTN  02/19/2008  Added header, additional logging, advertisement
019' //                          verification for every request.
020' //
021' // Customer History:
022' //
023' // ***** End Header *****
024' //***************************************************************************
025 
026'//----------------------------------------------------------------------------
027'//
028'//  Set global variables, used by the ZTIProcess function below.  (Note that
029'//  any changes to this script will require restarting the WDS service, since
030'//  the script is only loaded once and kept in memory as long as the service
031'//  is running.)
032'//
033'//  If ConfigMgr is running on the same server as WDS, the sProviderServer
034'//  value can be left blank and the sUsername and sPassword values must be
035'//  blank.
036'//----------------------------------------------------------------------------
037 
038Option Explicit
039 
040Dim sProviderServer
041Dim sSiteCode
042Dim sNamespace
043Dim sUsername
044Dim sPassword
045Dim sCollection
046 
047sProviderServer = ""
048sSiteCode = "ABC"
049sNamespace = "root\sms\site_" & sSiteCode
050sUsername = ""
051sPassword = ""
052sCollection = "ABC00004"   ' This must be a collection ID, not a collection name
053 
054'//----------------------------------------------------------------------------
055'//  Main routine
056'//----------------------------------------------------------------------------
057 
058ZTIProcess
059 
060Function ZTIProcess
061 
062    Dim sMacAddress
063    Dim sIPAddress
064    Dim sUUID
065    Dim sNetBiosName
066    Dim oLocator
067    Dim oSMS
068    Dim sQuery
069    Dim bFound
070    Dim iResourceID
071    Dim re
072    Dim oSite
073    Dim oParams
074    Dim oResult
075    Dim oLastError
076    Dim oClients
077    Dim oClient
078    Dim oCollection
079    Dim oNewRule
080    Dim oAdvertisement
081    Dim bTrying
082    Dim sAdvert
083    Dim i
084 
085    ' Initialization
086 
087    sMacAddress = PXE.MacAddress
088    sIPAddress = PXE.IPAddress
089    sUUID = PXE.UUID
090 
091    Set re = New RegExp
092    re.Pattern = ":"
093    re.Global = true
094    sNetBiosName = "MAC" & re.Replace(sMacAddress, "")
095 
096    ' Clear invalid UUID values
097 
098    If sUUID = "00000000-0000-0000-0000-000000000000" or sUUID = "FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF" then
099        sUUID = ""
100    End if
101 
102    ' Log details
103 
104    PXE.LogTrace "Processing request from MAC address = " & sMacAddress & ", IP address = " & sIPAddress & ", UUID = " & sUUID
105 
106    '//----------------------------------------------------------------------------
107    '//  Filter requests.  This may need to be customized if you only want some
108    '//  network segments to be supported.
109    '//----------------------------------------------------------------------------
110 
111    ' Ignore ConfigMgr "ping" requests
112 
113    If PXE.IPAddress = "127.0.0.1" or PXE.MacAddress = "FF:FF:FF:FF:FF:FF" then
114        PXE.LogTrace "Ignoring ConfigMgr ping request"
115        Exit Function
116    End if
117 
118    '//----------------------------------------------------------------------------
119    '//  Verify the computer is known to ConfigMgr
120    '//----------------------------------------------------------------------------
121 
122    ' Connect to the SMS provider
123 
124    Set oLocator = CreateObject("WbemScripting.SWbemLocator")
125    Set oSMS = oLocator.ConnectServer(sProviderServer, sNamespace, sUsername, sPassword)
126 
127    ' Build the query
128 
129    sQuery = "SELECT * FROM SMS_R_System WHERE MacAddresses = '" & sMacAddress & "'"
130    If sUUID <> "" then
131        sQuery = sQuery & " OR SMBIOSGUID = '" & sUUID & "'"
132    End if
133 
134    ' Process the query
135 
136    bFound = False
137    Set oClients = oSMS.ExecQuery(sQuery)
138    For each oClient in oClients
139        bFound = true
140        iResourceID = oClient.ResourceID
141        PXE.LogTrace "Found existing machine " & oClient.NetbiosName & " with ResourceID = " & iResourceID
142        sNetBiosName = oClient.NetbiosName
143        Exit For
144    Next
145 
146    '//----------------------------------------------------------------------------
147    '//  If necessary, add the computer to the ConfigMgr database
148    '//----------------------------------------------------------------------------
149 
150    If not bFound then
151 
152        PXE.LogTrace "Could not find machine with MAC address '" & sMacAddress & "' or SMBIOS UUID '" & sUUID & "'."
153 
154        ' Add the computer
155 
156        Set oSite = oSMS.Get("SMS_Site")
157        Set oParams = oSite.Methods_.Item("ImportMachineEntry").inParameters.SpawnInstance_()
158        oParams.NetbiosName = sNetBiosName
159        oParams.SMBIOSGUID = sUUID
160        oParams.MACAddress = sMacAddress
161        oParams.OverwriteExistingRecord = false
162 
163        On Error Resume Next
164        Set oResult = oSite.ExecMethod_("ImportMachineEntry", oParams)
165        If Err then
166            PXE.LogTrace "Error importing machine entry: " & Err.Description & " (" & Err.Number & ")"
167            Set oLastError = CreateObject("WbemScripting.SWbemLastError")
168            PXE.LogTrace "Last WMI error: " & oLastError.Description
169            Exit Function
170        End if
171        On Error Goto 0
172 
173        iResourceID = oResult.ResourceID
174        PXE.LogTrace "Added new machine with ResourceID = " & iResourceID
175 
176    End if
177 
178    '//----------------------------------------------------------------------------
179    '//  Check if the computer is a member of the specified collection
180    '//----------------------------------------------------------------------------
181 
182    If bFound then
183 
184        ' Build the query
185 
186        sQuery = "SELECT * FROM SMS_CM_RES_COLL_" & sCollection & " WHERE ResourceID = " & iResourceId
187 
188        ' Process the query
189 
190        bFound = False
191        Set oClients = oSMS.ExecQuery(sQuery)
192        For each oClient in oClients
193            bFound = true
194            PXE.LogTrace "Machine is already in collection " & sCollection
195            Exit For
196        Next
197 
198    End If
199 
200    '//----------------------------------------------------------------------------
201    '//  If necessary, add the computer to the specified collection
202    '//----------------------------------------------------------------------------
203 
204    If not bFound then
205 
206        ' Add the computer to the specified collection
207 
208        On Error Resume Next
209        Set oCollection = oSMS.Get("SMS_Collection.CollectionID='" & sCollection & "'")
210        If Err then
211            PXE.LogTrace "Error retrieving collection " & sCollection & ": " & Err.Description & " (" & Err.Number & ")"
212            Set oLastError = CreateObject("WbemScripting.SWbemLastError")
213            PXE.LogTrace "Last WMI error: " & oLastError.Description
214            Exit Function
215        End if
216        On Error Goto 0
217 
218        Set oNewRule = oSMS.Get("SMS_CollectionRuleDirect").SpawnInstance_()
219        oNewRule.ResourceClassName = "SMS_R_System"
220        oNewRule.RuleName = sNetBiosName
221        oNewRule.ResourceID = iResourceID
222 
223        On Error Resume Next
224        oCollection.AddMembershipRule oNewRule
225        If Err then
226            PXE.LogTrace "Error adding membership rule to " & sCollection & ": " & Err.Description & " (" & Err.Number & ")"
227            Set oLastError = CreateObject("WbemScripting.SWbemLastError")
228            PXE.LogTrace "Last WMI error: " & oLastError.Description
229            Exit Function
230        End if
231        On Error Goto 0
232        PXE.LogTrace "Added new membership rule to collection " & sCollection
233 
234    End if
235 
236    '//----------------------------------------------------------------------------
237    '//  Wait until an advertisement is seen.
238    '//----------------------------------------------------------------------------
239 
240    ' Check for the advertisements
241 
242    Set oAdvertisement = oSMS.Get("SMS_Advertisement")
243    i = 0
244    bTrying = True
245    Do While bTrying
246 
247        Set oParams = oAdvertisement.Methods_("GetAdvertisements").inParameters.SpawnInstance_()
248        oParams.ResourceID = iResourceID
249 
250        Set oResult = oAdvertisement.ExecMethod_("GetAdvertisements", oParams)
251        For each sAdvert in oResult.AdvertisementIDs
252            PXE.LogTrace "Found advertisement " & sAdvert
253            bTrying = False
254        Next
255 
256        i = i + 1
257        If bTrying and i > 10 then
258            PXE.LogTrace "Giving up after 30 seconds of checking for new advertisements."
259            bTrying = False
260        End if
261 
262        PXE.Sleep 3000
263 
264    Loop
265 
266    PXE.LogTrace "Exiting PXEFilter.vbs"
267 
268End Function

BDD2007 PXE-Filter

The PXE-filter in BDD2007 isn’t the best, and there are a few good-to-know things that can be hard to find…
The script \Microsoft Deployment Toolkit\Scripts\PXEFilter.vbs have a few parameters at the top that you need to configure:
1sProviderServer = "sccmserver.domain.com" ' FQDN of the SCCM-server
2sSiteCode = "CS1" ' Your site-code
3sNamespace = "root\sms\site_" & sSiteCode ' Do not change
4sUsername = "" ' Keep this one blank if the WDS and SCCM are installed on the same machine
5sPassword = "" ' Same as with sUsername
6sCollection = "CS10000E" ' ID of the deployment role
Remember: Do not add user and pass when you are on the same machine.
To debug the script, set the following key HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Tracing\WDSSERVER\EnableFileTracing to 1 and restart WDSServer
When you do, it will create a logile at %WinDir%\Tracing\WDSServer.LOG
If you need to add some debug-info in the script, add a line like this:
1PXE.LogTrace "My little debug-thingy"
Then you will se the following line in WDSServer.LOG:
[5440] 15:34:11: [WDSPXE] [Microsoft.BDD.PXEFilter] My little debug-thingy
 
And when you get it to work, the WDSServer.LOG should look like this:
[6288] 15:34:07: [WDSPXE] [Microsoft.BDD.PXEFilter] About to run script
[1812] 15:34:07: [WDSPXE] [Microsoft.BDD.PXEFilter] PXEFilter.vbs – Processing request from MAC address = 00:11:22:33:44:55, IP address = 0.0.0.0, UUID = 11111111-1111-1111-1111-111111111111
[1812] 15:34:07: [WDSPXE] [Microsoft.BDD.PXEFilter] PXEFilter.vbs – Could not find machine with MAC address ‘00:11:22:33:44:55′ or SMBIOS UUID ‘11111111-1111-1111-1111-111111111111′.
[1812] 15:34:07: [WDSPXE] [Microsoft.BDD.PXEFilter] PXEFilter.vbs – Added new machine with ResourceID = 46
[1812] 15:34:08: [WDSPXE] [Microsoft.BDD.PXEFilter] PXEFilter.vbs – Added new membership rule to collection CS10000E
[1812] 15:34:11: [WDSPXE] [Microsoft.BDD.PXEFilter] PXEFilter.vbs – Found advertisement CS120001
 
So what does the PXE-filter do?
1. Looks to see if the machine exist (via WMI to the SCCM-server)
2. If it exist, just exit the script. If not, create it.
3. Add it to the collection (Stated with the sCollection-setting)
4. Wait’s up to 15 seconds to see if there is an advertisment against the collection.
And then it’s done.
Note: I found this info when using SCCM2007 and BDD2007, might work with other versions…