Quick Tip: Complex Powershell in Run Commandline Step of ConfigMgr TS

I am using this more and more now, maybe just because i think it’s kinda cool. I wanted to use some Powershell in a Run Commandline Step in a TS, which used a couple of lines of code, had some double-quote characters, and i didn’t want (or was too lazy) to create a script in a package, update the DPs and use that. I just wanted to test the script, demo what it did, before i went down the package route.

Also, using double-quotes in the Commandline step annoys me , escape characters and so on; so i found this neat little trick.

Take the code you want to use, e.g.

$tsenv=new-object microsoft.sms.tsenvironment;$tsenv.Value(“SMSTSErrorDialogTimeout”)=0

and parse this to a string variable using something like a Here-String

$script = {$tsenv=new-object microsoft.sms.tsenvironment;$tsenv.Value(“SMSTSErrorDialogTimeout”)}.ToString()

then use the System Convert and Text Encoding classes to create a Base64String

$encCmd = [System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($script))

which gives

JAB0AHMAZQBuAHYAPQBuAGUAdwAtAG8AYgBqAGUAYwB0ACAAbQBpAGMAcgBvAHMAbwBmAHQALgBzAG0AcwAuAHQAcwBlAG4AdgBpAHIAbwBuAG0AZQBuAHQAOwAkAHQAcwBlAG4AdgAuAFYAYQBsAHUAZQAoACIAUwBNAFMAVABTAEUAcgByAG8AcgBEAGkAYQBsAG8AZwBUAGkAbQBlA
G8AdQB0ACIAKQA=

Now use that with Powershell in a Commandline step like this

powershell.exe -EncodedCommand JAB0AHMAZQBuAHYAPQBuAGUAdwAtAG8AYgBqAGUAYwB0ACAAbQBpAGMAcgBvAHMAbwBmAHQALgBzAG0AcwAuAHQAcwBlAG4AdgBpAHIAbwBuAG0AZQBuAHQAOwAkAHQAcwBlAG4AdgAuAFYAYQBsAHUAZQAoACIAUwBNAFMAVABTAEUAcgByAG8AcgBEAGkAYQBsAG8AZwBUAGkAbQBlAG8AdQB0ACIAKQA=

 

Watch for line breaks when you copy the Base64String. Otherwise, it works, and if you find it useful or just cool then i’m glad.

Incidentally, to Change the Base64String back to readable text:

[System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($encCmd))

Advertisement

Accessing the Task Sequence Environment in ConfigMgr

I had a question at CMCE Switzerland about how to get acces to the SMSTS variables through Powerhell, so here it is:

In either a script or at a Powershell command window, create a ComObject:

$tsenv = New-Object -ComObject Microsoft.SMS.TSEnvironment

you can use this to access and set Task Sequence variables:

$tsenv.Value(“OSDComputerName”)

will return the value of the OSDComputerName action variable

$tsenv.Value(“SMSTSErrorDialogTimeout”) = 0

sets the timeout on the error message box to something like 6 years (in seconds)

WSUS Post Configuration Failing

If WSUS Post Configuration tasks fail, check the log for a missing ContentDir parameter:
Start: LoadSettingsFromXml
Start: GetConfigValue with filename=UpdateServices-Services.xml item=ContentLocal
Value is true
End: GetConfigValue
Start: GetConfigValue with filename=UpdateServices-Services.xml item=ContentDirectory
Config file did not contain a value “ContentDirectory”
Microsoft.UpdateServices.Administration.CommandException: A required configuration value was not found in the system. This is usually caused by installing WSUS through PowerShell and not specifying a configuration file. Review the article Managing WSUS Using PowerShell at TechNet Library (http://go.microsoft.com/fwlink/?LinkId=235499) for more information on the recommended steps to perform WSUS installation using PowerShell.

This can happen if WSUS is added as a feature via Powershell, but also sometimes when using server manager.
The WSUS setup uses the UpdateServices-Services.xml from the ServerManager module. If the ContentDirectory node is empty ServerManager will use a root folder on the drive with most space (e:\wsus) and create a content directory there (e:\wsus\wsuscontent)
If this happens, even if you define a content folder in the wizard, then run the post install tasks with the following command lines (elevated) depending on whether using a WID or SQL Server

For installations using WID:
wsusutil postinstall CONTENT_DIR=E:\WSUS (or whatever parent folder for WSUSContent you’ve specified)

For installations using SQL Server:
wsusutil postinstall CONTENT_DIR=E:\WSUS SQL_INSTANCE_NAME=databaseServer[\instanceName]

Policypv Unknown SQL Error

Came across this nice little nugget today. The site seemed generally in order, mostly green, but the Policy Provider was in a Warning state. Looking in the status messages I found hundreds of Unknown SQL Errors. The policypv.log didn’t shed much more light on the supposed problem. Probably had the site been in a more intensive operational state someone would have noticed that they couldn’t add new Software Update packages, for instance.
The problem itself is a common one in the SQL Server world, stored procedures cannot be remotely executed. In this case the sp_updpolicyresmap.
And the most common cause is a SQL DBA or a Server Admin being proactive and improving the server by adding or reconfiguring new disks. To do this they detach the databases, make the changes and reattach. What sometimes happens though, is that for reasons unknown to me the Trustworthy state of the database reverts to false and the sa loses ownership.
Easy to fix though:
USE [databasename]
GO
EXECUTE sp_changedbowner ‘sa’

ALTER DATABASE [databasename]
SET TRUSTWORTHY ON

BitLocker in SCCM with 2nd HDD – NEW and REFRESH Scenarios

There are a couple of challenges when using BitLocker in ConfigMgr 2012. Using Pre-Provisioning and locking a 2nd HDD in REFRESH Scenarios is one such challenge.

Here is how i handle it.

Scenario 1: NEW – Single Disk

Background and overview: New PC or Laptop, single hard drive.

1. Create a PreInstall partition on the disk if there is no available partitions

2. Configure BIOS and TPM (see previous post)

3. Format and Partition Disk0 for use with BitLocker. Create a BDE partition with fixed size 500Mb, NTFS and store the drive letter as a variable BOOTPART. Create a System partition of 100% remaining space, NTFS, store drive letter as OSPART.

4. Pre-Provision BitLocker to “Logical drive letter stored in a a variable” – OSPART

5. At the end of the Task Sequence Enable BitLocker on “Current operating system drive”. Choose to wait for BitLocker to complete before continuing.

Scenario 2: REFRESH – Single Disk

Background and overview: Refresh PC or Laptop, single hard drive.

1. When started from Software Center, disable BitLocker on current operating system drive and reboot to WinPE.

2. If started from USB or PXE, use a script to unlock the operating system drive.

3. continue from step 2, Scenario 1.

Scenario 3: NEW – Additional Disk

Background and overview: New PC or Laptop, multiple hard drives.

1. Out of the box means started from USB or PXE, use a script to unlock OS and data drives.

2. continue from step 2, Scenario 1.

3. When finalizing BitLocker on the OS disk choose to continue on error. This because the attributes will be inconsistent after C, D and E drive letters are reassigned but the BitLocker process will finalize ok.

4. Enable BitLocker on the additional drive, choose either to wait for BitLocker to finish or continue and allow the drive to encrypt in the background. The machine will be usable but the 2nd disk will have limited availability until the process is finished which could be 20 – 50 minutes.

Scenario 4: REFRESH – Additional Disk

Background and overview: Refresh PC or Laptop, multiple hard drives.

1. When started from Software Center, disable BitLocker on current operating system drive and data drives and reboot to WinPE.

2. If started from USB or PXE, use a script to unlock the operating system drive and data drives.

3. continue from step 2, Scenario 1.

3. continue from step 2, Scenario 3.

Here are some screenshots of the TS:

BitlockerTS_1BitlockerTS_2BitlockerTS_3

Configuration Manager Community Event Jan 2014

Check all the slides from the event at www.configmgr.ch

I’d like to thank everyone who attended my session, they were a lovely bunch and I had some great discussions with people afterwards, here is a slide deck which I have since extended with my demo notes and script examples.

Hopefully we’ll be able to put up some web sessions, I will certainly post up here some follow ups on Pre-Execution Environment.

Big thanks to Mirko Colemberg for organising, to all the guys who travelled through: Daniel Bühlmann, Roman Andres, Thomas Kürth, Jürg Koller, Pascal Berger, my very esteemed colleague Roger Zander and not to forget David O’Brien and Wally Mead who delivered great sessions.

https://drive.google.com/file/d/0B7oCZsxx9d5dUDhaekoyeUhweDA/edit?usp=sharing

Content download problems with large packages in ConfigMgr 2012

The customer was trying to install an AutoCAD type application via Configuration Manager 2012 (SP1 CU2) and the installation kept hanging in Software Center with the message “downloading data”.

The background is that this application had previously worked ok, but since then the source files have been altered and updated on the DPs. The source data is itself around a whopping 9Gb.

So let’s go through the logs.

AppDiscovery, AppEnforce and AppIntentEval all look normal. Application is not detected and will be installed.

Execmgr makes a call to CAS requesting content. So far so good.

CAS requests content locations and receives 3 valid DPs.

CAS submits a CTM (Content Transfer Manager) job, and receives confirmation of the locations and submits a DTS (Data Transfer Service) job.

Now the DTS starts the download and immediately hits a problem.

GetDirectoryList_HTTP(‘http://dp:80/SMS_DP_SMSPKG$/Content_45106a31-c15b-4d29-ba68-97e1b97a5e9e.1’) failed with code 0x80004005.

Error retrieving manifest (0x80004005).  Will attempt retry 1 in 30 seconds.

Sure enough it retries only to get:

Non-recoverable error retrieving manifest (0x80004005).

And this happens in turn for each available DP. Unfortunately Software Center will just sit there doing nothing. This last point may be because the installation is running in a task sequence but let’s not dwell on that for now.

So obviously a problem with the content on the DPs. Agreed?

Redistribute application to DPs – same error

Remove application from all Dps and redistribute – same error

Zip the source from 9gb down to 4Gb, remove from DPs and redistribute – same error, but wait, when i redistributed to the DPs it still took a long time, not half the time as i expected.

I had to get the Content ID from the DTS log and then look at the ContentLibrary on the DP. Found the content but it didn’t match the source. In fact it didn’t match any updated source, seemed to be the original content from before the first update. Very strange.

So to look at how distmgr and ContentLibrary work in more detail:

Source is copied to the “primary site server” and stored in a ContentLibrary there, even though there is no DP on this server. This is a small throwback to SCCM 2007 and is unavoidable. Anyway, from there the content is copied to the DPs and stored in ContentLibrary. Removing an application from the DP goes quite quickly in the console but looking at the  distmgr log and smsdpprov log on the DP you can see that the data itself is not fully removed for some time after, depending on the size of the content. If you redistribute the content before it is fully removed from ContentLibrary then distmgr will skip copying the files that already exist. Also, distmgr will copy the content from the ContentLibrary on the primary server, if it exists there, rather than directly from the source. So in actual fact, with a 9Gb application the source was never correctly updated on the DPs – it’s very difficult to say exactly which files didn’t match the hash values in the manifest but obviously we need to completely this application’s content from the DPs, from the primary server ContentLibrary and wait until everything is completely gone before redistributing.

Easier said than done. To remove from the DPs is not so difficult, just as with any normal application. But make sure that distmgr and smsdpprov confirm it is gone, AND check manually in ContentLibrary in DataLib under the ContentID to make sure it is physically gone. For 9Gb this can take about 30 minutes.

Then have a look at the ContentLib on the primary server. It hasn’t detected that it needs to update from the source so you need to trigger that. Here is what i did although maybe there is a better way, i had run out of patience by this point so i didn’t wait to see if it would also remove itself.

I removed all previous revisions of the application from revision history. Then changed the source path on the deployment type to point to an empty folder. Now monitoring distmgr i can see he has spotted this and creates a new content instance in the library with a new ContentID. The old ContentID however remains and is flagged as orphaned. Check ContentLibrary for the physical presence and this query against the DB:

select * from OrphanedContents

After a maximum of 60 minutes, the content cleanup cycle will run on the server and remove these orphaned contents. That’s about how long it takes to find out that there is very little documentation on the content cleanup task on the internet, and very little help to be found searching for “delete orphaned content sccm 2012” in Bing or Google or whatever… It helps pass the time though.

So now we have a large application with an empty source path, and not distributed to DPs. And checking that we can see that there are absolutely no more traces on DPs, in ContentLibraries, or in the DB.

Now set the source back to the correct source path on the deployment type. And wait until this is completely updated on the primary site in ContentLibrary. I didn’t wait this time for the cleanup task but i checked back later and references to the empty source where gone.

Check the ContentLibrary on the primary server, you need the new ContentID from distmgr.log, to see if the new content is physically there. Once it is distribute to DPs. Again check distmgr, also PkgXferMgr.log, on the primary server and smsdpprov.log on a DP until they are finished processing the content. Check the content is physically in the ContentLibrary in DataLib under the ContentID.

Now try installing the application again on the client. This time it installs no problem.

A couple of things to point out here:

This deployment is an application delivered via task sequence but it applies to pure AppModel deployments as well.

We have a primary server which serves as SMSProvider and the DPs are all only on separate boxes.

The problem is here when you have a very large amount of data in the source and you update through the console quicker than it actually takes for the processes to finish. On smaller content this may be ok but the larger the packet the more chance that something will get skipped in the file copy process.

Create Collections in ConfigMgr 2012 with Powershell

Not got time to write the usual three-page idiosyncratic blurb, here’s a script which will create four different collections for a pre-existing Application and move the collections to specific Folders (As usual the word wrap has probably screwed it up a little so i have attached the script as ps1 as well here):

Function CreateCollection($collName,$limitingColl,$container,$ud_Scope){

$collInstance = ([wmiclass]”\\server\root\sms\site_001:sms_collection”).CreateInstance()

$collInstance.Name = $collName

$collInstance.LimitToCollectionID = $limitingColl

if($ud_Scope -eq “device”){

$collInstance.CollectionType = 2

}elseif($ud_Scope -eq “user”){

$collInstance.CollectionType = 1

}

if(gwmi -ComputerName server -Namespace root\sms\site_001 -Class sms_collection | ?{$_.Name -eq $collName}){

“Collection $collName already exists”

}else{

$collInstance.Put()

$collInstance.Get()

$containerItemInstance = ([wmiclass]”\\server\root\sms\site_001:sms_objectcontaineritem”).CreateInstance()

$containerItemInstance.ContainerNodeID = $container

$containerItemInstance.InstanceKey = $collInstance.CollectionID

if($ud_Scope -eq “device”){

$containerItemInstance.ObjectType = 5000

$containerItemInstance.ObjectTypeName = ‘SMS_Collection_Device’

}elseif($ud_Scope -eq “user”){

$containerItemInstance.ObjectType = 5001

$containerItemInstance.ObjectTypeName = ‘SMS_Collection_User’

}

$containerItemInstance.SourceSite = ‘001’

$containerItemInstance.Put()

}

}

$scope = Read-Host -Prompt “Enter Scope ID (DEV,INT,PRD)”

gwmi -ComputerName server -Namespace root\sms\site_001 -Class sms_objectcontainernode | ?{$_.Name -eq ‘Software Distribution’} | %{

$parentContainerID = $_.parentContainerNodeID

if(gwmi -ComputerName server -Namespace root\sms\site_001 -Class sms_objectcontainernode | ?{$_.ContainerNodeID -eq $parentContainerID -and $_.Name -eq $scope}){

gwmi -ComputerName server -Namespace root\sms\site_001 -Class sms_objectcontainernode | ?{$_.Name -eq ‘Software Distribution’ -and $_.parentContainerNodeID -eq $parentContainerID -and $_.ObjectTypeName -eq “SMS_Collection_Device”} | %{

$parentContainerID = $_.ContainerNodeID

gwmi -ComputerName server -Namespace root\sms\site_001 -Class sms_objectcontainernode | ?{$_.Name -eq ‘Software Requests’ -and $_.parentContainerNodeID -eq $parentContainerID -and $_.ObjectTypeName -eq “SMS_Collection_Device”} | %{

$devContainerID = $_.ContainerNodeID

}

}

gwmi -ComputerName server -Namespace root\sms\site_001 -Class sms_objectcontainernode | ?{$_.Name -eq ‘Software Distribution’ -and $_.parentContainerNodeID -eq $parentContainerID -and $_.ObjectTypeName -eq “SMS_Collection_User”} | %{

$parentContainerID = $_.ContainerNodeID

gwmi -ComputerName server -Namespace root\sms\site_001 -Class sms_objectcontainernode | ?{$_.Name -eq ‘Software Requests’ -and $_.parentContainerNodeID -eq $parentContainerID -and $_.ObjectTypeName -eq “SMS_Collection_User”} | %{

$usrContainerID = $_.ContainerNodeID

}

}

}

}

$devLimitingCollID = (gwmi -ComputerName server -Namespace root\sms\site_001 -Class sms_collection | ?{$_.Name -eq “Root-$scope”} | Select CollectionID).CollectionID

$usrLimitingCollID = “SMS00002”

 

gwmi -ComputerName server -Namespace root\sms\site_001 -Class sms_applicationlatest | ?{$_.SecuredScopeNames -contains $scope -and $_.LocalizedDisplayName -eq ‘OracleClient’} | select LocalizedDisplayName | %{

$appName = $_.LocalizedDisplayName

$collectionName = “$appName – Install Device”

CreateCollection $collectionName $devLimitingCollID $devContainerID “device”

$collectionName = “$appName – Uninstall Device”

CreateCollection $collectionName $devLimitingCollID $devContainerID “device”

$collectionName = “$appName – Install User”

CreateCollection $collectionName $usrLimitingCollID $usrContainerID “user”

$collectionName = “$appName – Uninstall User”

CreateCollection $collectionName $usrLimitingCollID $usrContainerID “user”

}