PowerShell: Modify Source Location for SCCM Drivers

I recently ran into an issue where eh hmm.. someone (not sure who but his initials are WB) imported a new set of drivers from a UNC path that was named incorrectly.¬† ūüėź

To be exact, the directory was named “\\DFS\NameSpace\Share$\OSD\Win7\x64\DriverSrc\Lenovo S32_x64_R01” when in reality it was supposed to be “\\DFS\NameSpace\Share$\OSD\Win7\x64\DriverSrc\Lenovo E32_x64_R01″.

There are quite a few drivers that were imported for this particular model and there is no way in the console to bulk change the source path so after renaming the source folder to E32, off to PowerShell I went.

As you might guess I opened the PowerShell (x86) Console from the SCCM Admin Console (Still on Sp1 CU3 so no x64 PowerShell for me yet).

First I need to retrieve the driver objects that have the invalid path:
$Drivers = Get-CMDriver | Where-Object ($_.ContentSourcePath -like “*S32_x64*”}

This process takes a while as I have a lot of drivers.  If your really bored you can watch the SMSProv.log instead of stare at the PowerShell console waiting for the command to end.

Once the command completes I  have a new variable ($Drivers ) filled with all the driver objects I need to target to fix this mishap created by whoever that attention deficit admin was.  (*cough*).  For sanity sake I echo the contents out to the screen to validate I have only what I need in the variable:

$Drivers | Select-Object ContentSourcePath

I now know I have all the desired drivers that I need to modify.  My goal is to simply replace S32 with E32 in the existing path.  The command I used:

foreach ($driver in $xBackupDrivers) {$oldPath = $driver.ContentSourcePath; Write-Host “OLDPATH: $oldPath”; $NewPath = $driver.ContentSourcePath -replace “S32″,”E32”; Write-Host “NEWPATH: $NewPath”; Set-CMDriver -Id $driver.CI_ID -DriverSource $NewPath}

An easier way to read this:

foreach ($driver in $xBackupDrivers) {
    $oldPath = $driver.ContentSourcePath
¬†¬†¬† Write-Host “OLDPATH: $oldPath”
¬†¬†¬† $NewPath = $driver.ContentSourcePath -replace “S32″,”E32”
¬†¬†¬† Write-Host “NEWPATH: $NewPath”
    Set-CMDriver -Id $driver.CI_ID -DriverSource $NewPath
}

I have highlighted the commands that are doing the actual work.¬† I simply replace the text into a new variable and pass that to the “Set-CMDriver” cmdlet (Along with the driver CI_ID).

About¬†30 seconds¬†later the drivers have the correct source path.¬†¬†I’m now off¬†to reprimand that SCCM Admin!

 

Advertisements

PowerShell: Get logged on user remotely

Ever need to determine who is logged in to a remote computer?¬† I found myself frequently using¬†the Sysinternals “psloggedon” utility.¬† Nothing wrong with this utility and it continues to work on modern OS’s however a single line of PowerShell code can accomplish the same task: (Of course this command requires administrative rights on the remote computer.¬† ;-))

Get-WmiObject Win32_ComputerSystem -ComputerName ComputerName | select username

LoggedOn

 

Check out the Sysinternal TechNet site.  Still some handy tools:  http://technet.microsoft.com/en-us/sysinternals/

 

 

SCCM: Migrating to a new source code server

I have run across this several time in the past where the source code location for your packages (and now drivers as well) needs to be moved to a new server. I have 3 scripts I use (Could be combined into one if you like) that will allow you to change the location for each object.

Script 1: remapPkgs.vbs
This script will modify the source path for all (non Driver related) packages.

Script 2: remapDrivers.vbs
This script will remap all the source locations for the drivers that have been imported.

Script 3: remapDriverPkgs.vbs
This script will remap all the source locations for your driver packages.

VERY IMPORTANT – When you run the remapPkgs.vbs and the remapDriverPkgs.vbs it will trigger a Distribution Point update for all your packages. You will likely want to run this after hours or over the weekend depending on how many packages you have and what your bandwidth is to your DP’s.

*UPDATE* – Couple of things I noticed in my environment after running these scripts.
1. We use Nomad Branch from 1E. This adds a tab to your packages where you specify if you want to enable Nomad. After running the scripts I had to go back and reset this on all my packages.
2. Much more importantly we also discovered that these scripts re-enabled the setting for all of our App-V packages to “remove when no longer advertised”. Under normal day to day operations this setting is desired for us however as part of our re-assigning the clients to a new site code all local policy is deleted and re-downloaded from the new server. Well with that option still enabled it would remove all App-V apps from all machines as the policy would get deleted and it would not come back automatically. This is why we removed that option. Long story short we had to go back and remove the option again from all App-V packages after migrating the source code.

Another important note regarding App-V packages:
Although Microsoft does not give you the option to modify the source directory of your Virtual Applications via the Admin Console the script above will remap your App-V packages as well. In my environment however I was required to touch each Virtual App after the script and “re-import” the package if you will. This was achieved by clicking the Properties of the Virtual App, Click Data Source, then click “Set”. In the “Virtual Application package source directory” click “Browse” and specify the original location of the xml file used to create the package. I found that in most circumstances I needed to delete the contents of the “ConfigMgr data source directory” prior to browsing to the xml file. Once you import the package again the contents of the source directory get re-created and the package resent to the DP’s.

The syntax for the scripts are included in the script header however its pretty simple (must be run from the SCCM server itself):

cscript scriptname.vbs NEWSERVERNAME

So example: My current source path that I want to change is:
\\SERVER01\SOURCE$\SoftwareInstalls\Adobe_AcrobatReader_9.0_R01

I would run the script like so:
cscript remapPkgs.vbs SERVER02

This will change the source directory to:
\\SERVER02\SOURCE$\SoftwareInstalls\Adobe_AcrobatReader_9.0_R01
(And trigger an update to the DP)

Again these scripts are setup to do all packages in one fell swoop and designed to only need to replace the server name. You could easily modify the scripts to change the share name, etc as needed.

I would also recommend commenting out the “put_” statement and run the script to verify it works prior to actually making the changes.
_________________________________________________
Script 1 (Mind word wrapping)
'///////////////////////////////////////////////////
'
' SYNTAX: cscript remapPkgs.vbs NEWSERVERNAME
'
'///////////////////////////////////////////////////////////
Dim oWbemServices
If UCase(Right(Wscript.FullName, 11)) = "WSCRIPT.EXE" Then
Wscript.Echo "This script must be run under CScript."
Wscript.Quit
End If
If WScript.Arguments.Count 1 Then
WScript.Echo "Wrong number of arguments were passed."
WScript.Quit
End If
'====================================================
'== Execute SUB to connect to SCCM Provider
'====================================================
CONNSCCM()
For Each oPackage In oWbemServices.execquery("select * from sms_package")
sOldPathString = oPackage.pkgsourcepath
If InStr(sOldPathString,"\\") Then
iStart = InStr(3,sOldPathString,"\")
sNewPathString = Right(sOldPathString,(LEN(sOldPathString)-iStart))
sNewPathString = "\\" & WScript.Arguments(0) & "\" & sNewPathString
WScript.Echo "Setting Package Path for " & oPackage.Name & " from " & sOldPathString & " to " & sNewPathString
oPackage.pkgsourcepath = sNewPathString
oPackage.put_
End If
Next
WScript.Echo "Done"
'====================================================
' SUB: Connects to the SMS Provider
'====================================================
Sub CONNSCCM()
Set oWbemLocator = CreateObject("WbemScripting.SWbemLocator")
Set oWbemServices = oWbemLocator.ConnectServer(".", "root\sms")
Set oSCCMProvLoc = oWbemServices.InstancesOf("SMS_ProviderLocation")
For Each oLoc In oSCCMProvLoc
If oLoc.ProviderForLocalSite = True Then
Set oWbemServices = oWbemLocator.ConnectServer(oLoc.Machine, "root\sms\site_" + oLoc.SiteCode)
End If
Next
End Sub

Script 2 (Mind word wrapping)
'///////////////////////////////////////////////////
'
' SYNTAX: cscript remapDrivers.vbs NEWSERVERNAME
'
'///////////////////////////////////////////////////////////
Dim oWbemServices
If UCase(Right(Wscript.FullName, 11)) = "WSCRIPT.EXE" Then
Wscript.Echo "This script must be run under CScript."
Wscript.Quit
End If
If WScript.Arguments.Count 1 Then
WScript.Echo "Wrong number of arguments were passed."
WScript.Quit
End If
'====================================================
'== Execute SUB to connect to SCCM Provider
'====================================================
CONNSCCM()
For Each oPackage In oWbemServices.execquery("select * from sms_Driver")
sOldPathString = oPackage.contentsourcepath
If InStr(sOldPathString,"Latitude 13") Then
iStart = InStr(3,sOldPathString,"\")
sNewPathString = Right(sOldPathString,(LEN(sOldPathString)-iStart))
sNewPathString = "\\" & WScript.Arguments(0) & "\" & sNewPathString
WScript.Echo "Setting Package Path for " & oPackage.LocalizedDisplayName & " from " & sOldPathString & " to " & sNewPathString
'WScript.Echo sOldPathString
oPackage.contentsourcepath = sNewPathString
oPackage.put_
End If
Next
WScript.Echo "Done"
'====================================================
' SUB: Connects to the SMS Provider
'====================================================
Sub CONNSCCM()
Set oWbemLocator = CreateObject("WbemScripting.SWbemLocator")
Set oWbemServices = oWbemLocator.ConnectServer(".", "root\sms")
Set oSCCMProvLoc = oWbemServices.InstancesOf("SMS_ProviderLocation")
For Each oLoc In oSCCMProvLoc
If oLoc.ProviderForLocalSite = True Then
Set oWbemServices = oWbemLocator.ConnectServer(oLoc.Machine, "root\sms\site_" + oLoc.SiteCode)
End If
Next
End Sub

Script 3 (Mind word wrapping)
'///////////////////////////////////////////////////
'
' SYNTAX: cscript remapDriverPkgs.vbs NEWSERVERNAME
'
'///////////////////////////////////////////////////////////
Dim oWbemServices
If UCase(Right(Wscript.FullName, 11)) = "WSCRIPT.EXE" Then
Wscript.Echo "This script must be run under CScript."
Wscript.Quit
End If
If WScript.Arguments.Count 1 Then
WScript.Echo "Wrong number of arguments were passed."
WScript.Quit
End If
'====================================================
'== Execute SUB to connect to SCCM Provider
'====================================================
CONNSCCM()
For Each oPackage In oWbemServices.execquery("select * from sms_DriverPackage")
sOldPathString = oPackage.PkgSourcePath
If InStr(sOldPathString,"\\") Then
iStart = InStr(3,sOldPathString,"\")
sNewPathString = Right(sOldPathString,(LEN(sOldPathString)-iStart))
sNewPathString = "\\" & WScript.Arguments(0) & "\" & sNewPathString
WScript.Echo "Setting Package Path for " & oPackage.Name & " from " & sOldPathString & " to " & sNewPathString
'WScript.Echo sOldPathString
oPackage.PkgSourcePath = sNewPathString
oPackage.put_
End If
Next
WScript.Echo "Done"
'====================================================
' SUB: Connects to the SMS Provider
'====================================================
Sub CONNSCCM()
Set oWbemLocator = CreateObject("WbemScripting.SWbemLocator")
Set oWbemServices = oWbemLocator.ConnectServer(".", "root\sms")
Set oSCCMProvLoc = oWbemServices.InstancesOf("SMS_ProviderLocation")
For Each oLoc In oSCCMProvLoc
If oLoc.ProviderForLocalSite = True Then
Set oWbemServices = oWbemLocator.ConnectServer(oLoc.Machine, "root\sms\site_" + oLoc.SiteCode)
End If
Next
End Sub

VBScript: Run Optional SCCM Advertisements

Here’s a fairly simple script that will check for and execute all optional advertisments on a client machine. This could be modified to include checks for adverts that have already ran and not rerun or put a GUI around it to choose which adverts to run selectively.
If the code has not been downloaded to the client yet a content retrieval will be made automatically prior to running.

CAREFUL OF WORD WRAPPING

'==========================================================================
'
' VBScript Source File -- Created with SAPIEN Technologies PrimalScript 2009
'
' NAME: RunOptionalAdvertisements.vbs
'
' AUTHOR: William Bracken
' DATE : 10/12/2009
'
' COMMENT: Run optional advertisments
'
'==========================================================================
Dim oUIResource, programArray(), fs, f
Dim oPrograms, x, i, strLogFile, strFileName, UserTempPath, WindowsTempPath
Dim oProgram, programId, packageId, myProgram, WinTempLog, UsrTempLog
Const ForReading = 1, TemporaryFolder = 2, ForAppending = 8
Set fs = CreateObject("Scripting.FileSystemObject")
Set WSHShell = CreateObject("WScript.Shell")
windir = WSHShell.ExpandEnvironmentStrings("%WINDIR%")
UserTempPath = WSHShell.ExpandEnvironmentStrings("%TEMP%")
WindowsTempPath = windir & "\temp"
strFileName = "RunOptionalAdverts.log"
WinTempLog = WindowsTempPath & "\" & strFileName
UsrTempLog = UserTempPath & "\"& strFileName
strLogFile = UsrTempLog

CLEANLOG strLogFile
COLLECTMSG "Begin Logging", ""

' Validate connection to SCCM Client
Set oUIResource = CreateObject ("UIResource.UIResourceMgr")
If oUIResource Is Nothing Then
COLLECTMSG "CONNECT TO SCCM CLIENT", "Could not create Resource Object - quitting"
wscript.echo "Could not create Resource Object - quitting"
WScript.Quit
End If

'==========================================================================
' Begin Process
'==========================================================================
' Call ShowPrograms routine to get array of programs available
ShowPrograms

' Split Array And Call Sub to install
For Each program In programArray
strStrings = Split (program,";")
programId = strStrings(0)
packageId = strStrings(1)
' Call RunProgam routine to invoke installation
'RunProgram programId,packageId
Next

' Cleanup
Set oProgram=Nothing
Set oUIResource=Nothing

'==========================================================================
' Routines
'==========================================================================
Sub ShowPrograms
On Error Resume Next

Set oPrograms = oUIResource.GetAvailableApplications

If oPrograms Is Nothing Then
COLLECTMSG "SHOWPROGRAMS", "Failed to get programs object - quitting"
wscript.echo "Failed to get programs object - quitting"
Set oUIResource=Nothing
Exit Sub
End If
strCount = oPrograms.Count
COLLECTMSG "SHOWPROGRAMS", "There are " & strCount & " programs"
wscript.echo "There are " & strCount & " programs"
wscript.echo
strCount = strCount - 1

ReDim programArray(strCount)
x = 0
For Each oProgram In oPrograms
COLLECTMSG "SHOWPROGRAMS", "ProgramName:" & oProgram.Name & ";" & "PackageID:" & oProgram.PackageId
WScript.Echo "Program Name: " & oProgram.Name
WScript.Echo " Package ID: " & oProgram.PackageId
' build array
programArray(x) = oProgram.Name & ";" & oProgram.packageId
x = x + 1
Next
End Sub

Sub RunProgram(programId,packageId)
On Error Resume Next
' #region Disabled code
' if oUIResourceMgr.IsMandatoryProgramPending = 1 Then
' Wscript.Echo "Mandatory program pending. Try again later."
' Set oUIResource=Nothing
' Exit Sub
' End If
' #endregion

' Get Program object
Set oProgram = oUIResource.GetProgram(programId,packageId)
'WScript.Echo "oProgram: " & oProgram.Name
if oProgram is Nothing Then
COLLECTMSG "RUNPROGRAMS", "Couldn't get the program"
WScript.Echo "Couldn't get the program"
Set oUIResource=Nothing
Exit Sub
End If

'When was the program last ran?
Set myProgram = oUIResource.GetProgram(programId, packageId)
oldTime = myProgram.LastRunTime
COLLECTMSG "RUNPROGRAMS", "Last Run Time: " & oldTime
WScript.Echo oldTime

' Execute program
COLLECTMSG "RUNPROGRAMS", "Running program: " & programId & " " & packageId
Wscript.Echo "Running program: " & programId & " " & packageId
oUIResource.ExecuteProgram programId, packageId, True

' Wait for last ran to change...
Set myProgram = oUIResource.GetProgram(programId, packageId)
Do While myProgram.LastRunTime = oldTime
Wscript.Sleep(2500)
Set myProgram = oUIResource.GetProgram(programId, packageId)
Loop
COLLECTMSG "RUNPROGRAMS", "Return Code: " & Err
WScript.Echo "Return Code: " & Err
End Sub

'*************************************************************
'*** Method: COLLECTMSG
'*** Description: Collections Errors for logging to log file
'*************************************************************
Sub COLLECTMSG(szLocation, errmsg)
'on error resume next
'Dim f
strmsg=errmsg
Set f = fs.OpenTextFile(strLogFile, ForAppending, True)
strmsg = Now & " | " & szLocation & " | "& errmsg
if err0 then
strmsg=strmsg & vbCrLf _
& "Error | " & Hex(Err.Number) & " h (" & CStr(Err.Number) & ") | Description |" & Err.Description
end if
f.Writeline strmsg
f.Close
strmsg=""
Err.Clear
End Sub

Sub CLEANLOG(strLogFile)
If fs.FileExists(strLogFile) Then
fs.DeleteFile strLogFile
End If
End Sub

Export Computer list from AD

I find myself using this quite often so I figured I would post here for a nice copy and paste for future uses.¬† ūüėČ
*Requires Windows 2003 Admin tools (adminpak.msi) to be installed

Export computer objects from a specified OU and all sub OU’s(careful of wordwrap):

dsquery computer “ou=laptops,ou=devices,ou=site,dc=subdomain,dc=domain,dc=com” -limit 0 -o samid >>Laptops.txt

-limit 0 tells the script to return ALL matching values (Default is 100)
-o samid tells the script to return the “friendly” name. (Default is the full DN)

Obviously replace the values with ones specific to your environment.¬† ūüėČ

PXEFilter.vbs update to auto clear last PXE Advert

** This script has been deprecated with the release of SCCM 2007 R2.¬† It supports “Unknown Machines” inherently

I have modified the PXEFilter.vbs that ships with MDT 2008 to automatically clear the last PXE advertisement of the machine that is PXE booting. This allows me to set a mandatory assignment for my OS Deployment and removes the requirement for field techs to have to open the admin console to re-image a machine via PXE.

The section I added was:

‘//————————————————————-
‘// Clear Last PXE Advertisement
‘//————————————————————-
On Error Resume Next
Dim resources
Dim InParams

‘ Set up the Resource array parameter.
resources = Array(1)
resources(0) = iResourceID

Set InParams = oSMS.Get(“SMS_Collection”).Methods_(“ClearLastNBSAdvForMachines”).InParameters.SpawnInstance_
InParams.ResourceIDs = resources

oSMS.ExecMethod “SMS_Collection”,”ClearLastNBSAdvForMachines”, InParams

if Err.number <> 0 Then
PXE.LogTrace “Failed to clear PXE advertisement for resource: ” & iResourceID
Else
PXE.LogTrace “Clear PXE Advertisement Completed successfully for resource: ” & iResourceID
End If

Windows PE 2.0 AD Scripting requirements

 From the Vista install.wim or from a Vista machine retrieve the following files (located in the System32 directory):

adsldp.dll
adsnt.dll
mscoree.dll
mscorier.dll
mscories.dll

Mount your Windows PE boot image using imagex.
Example:
imagex /mountrw c:\PathToPEWim\WinPE.wim 1 c:\Mounted_Images

Copy the dll files above to c:\Mounted_Images\Windows\System32

Using regedit, load the SOFTWARE hive from your PE 2.0 Boot WIM and call it PE_Software.
(Location of the Software hive:
c:\Mounted_Images\Windows\System32\config\SOFTWARE)

Save the text below as a reg file. With the PE_Software hive loaded, import the registry key. UNLOAD the hive when finished to save the changes.
Unmount and commit changes to your PE wim image. Continue reading