4/07/2011

Restricted Users cannot create a Direct Membership Rule on a Collection programmatically in SCCM

Consider the following scenario:
You have a User in SCCM 2007 who has only restricted permissions in SCCM. For example the User can just read and modify some specific collections, import computers and create Collection Membership Rules. When the User is using the SCCM Console there’s no problem and all tasks can be performed.
You have written a tool (vbscript, vb.net or whatever) which makes this User possible to perform some tasks very easy and fast on your SCCM Server. The User can import a new machine (with name and mac address), but when trying to create a Direct Membership Rule the tools crashes with “Generic failure”.
If you check the smsprov.log file on your SCCM Server you get this error message:
User … has no read resource rights in any collection for this ResourceID

The problem is that you cannot define a “Default Collection” in the SCCM API where the machine is member of from the beginning. As the User has no Read Rights on the all Collections Class it cannot find that Machine and so it cannot create the Membership Rule. Even if you catch the machine’s ResourceID from the ImportMachineEntry function when creating the machine you the tool crashes.

Either you give the User read Permissions for the all Collections Class Rights, (but then the User can see all Machines in SCCM) or you use the following workaround:
When importing the machine entry there’s created a Status Message on your Site Server. This message looks like listed below and has the Message ID 30213:
User "XYZ" imported machine at site "SiteServer - SiteCode" (NetbiosName=YourMachineName, MACAddress=F7:E8:D3:A9:84:FE, SMBIOSGUID=).
Now create a Status Filter Rule which gets triggered from when this MessageID occurs and start a script with the following command line:
cscript.exe YourScript.vbs %msgis04
%msgis04 later contains the Machine name which you have created.
Now the script runs with the Local System Account of your SCCM Server and must create a Collection Membership Rule on any collection:

Any part of this script would look like this:

strComputername = Wscript.Arguments(0)
strCollectionID = “XYZ“
Set instCollection = objSWbemServices.Get("SMS_Collection.CollectionID='" &strCollectionID & "'")
Set instDirectRule = objSWbemServices.Get("SMS_CollectionRuleDirect").SpawnInstance_
instDirectRule.ResourceClassName = "SMS_R_System"
instDirectRule.ResourceID = NameTOResourceID(strComputerName)
instDirectRule.RuleName = strComputername
instCollection.AddMembershipRule(instDirectRule)


After the script has been executed the Users tool should be able to create the Direct Membership Rule.

AddOn: Maybe you have to build in a “wait-routine” in the Users tool until the Machine’s name get resolved into a ResourceID

Do
objSWbemServices.ExecQuery("SELECT * FROM SMS_R_System WHERE Name = '" & strComputerName & "'"
Loop While objSWbemServices.Count = 0

4/06/2011

Switch from Local System to User context

Sometimes there’s a need to switch from Local System context to the current logged on User.
The following article describes how to achieve this.

In short words the following happens:
1. Something is executed in local system context
2. At the end a EventLog entry is written
3. A Scheduled Tasks gets trigged by the created EventLog entry
4. The Scheduled Tasks which is running in User context executes another script

One example is e.g. to inform Users about an Installation which was executed from the Local System Account. The Application Deployment process executes the CreateEventToInform.vbs Script at the end of the installation to create the EventLog entry for informing the User

CreateEventToInform.vbs
Dim objShell : Set objShell = CreateObject("WScript.Shell")
Dim strMessage : strMessage = "Dear User this is a message from your local system"
objShell.LogEvent(4, strMessage)


InformUser.vbs
Dim strMessage
Set objWMIService = GetObject("winmgmts:" & "{impersonationLevel=impersonate}!\\" & "."
& "\root\cimv2")
Set colLoggedEvents = objWMIService.ExecQuery("Select * from Win32_NTLogEvent Where Logfile = 'Application' and SourceName='WSH' and EventCode=4 and Type='Information'")

For Each objEvent In colLoggedEvents
strMessage = objEvent.Message
Exit For
Next

MsgBox(strMessage, vbInformation + vbOKOnly, "Your Company")


It could also be used as an immediately executed “ActiveSetup” for the current logged User. The Application Deployment process executes the CreateEventToExecute.vbs Script at the end of the installation to create the EventLog entry for performing a Active Setup for the current User

CreateEventToExecute.vbs
Dim objShell : Set objShell = CreateObject("WScript.Shell")
Dim strCommandLine : strCommandLine = "msiexex /fup "
objShell.LogEvent(4, strCommandLine)


ExecuteUserPart.vbs
Dim objShell : Set objShell = CreateObject("Wscript.Shell")
Dim strCommandLine
Set objWMIService = GetObject("winmgmts:" & "{impersonationLevel=impersonate}!\\" & "." & "\root\cimv2")
Set colLoggedEvents = objWMIService.ExecQuery("Select * from Win32_NTLogEvent Where Logfile = 'Application' and SourceName='WSH' and EventCode=4 and Type='Information'")

For Each objEvent In colLoggedEvents
strCommandLine = objEvent.Message
Exit For
Next

objShell.Run(strCommandLine, 1, False)


To make the whole think work we have to create a trigger which reacts to the EventLog Entry which was created. Create a Scheduled Tasks which is executed in the User context as listed below for this

Note: You can deploy scheduled tasks to many computer automatically e.g. with Group Policies

Now as soon as the EventID is written the Scheduled Tasks executes the below listed command



Further thoughts:
You could create a whole Application which is written variable so that you can perform different actions in User context. To achieve this easily you could tag the EventLog Message with specific keywords. Later the Application parses the EventLog Message and you can react on this. So you can use it for different scenarios. Example:

WriteEventLog.vbs
Dim objShell : Set objShell = CreateObject("WScript.Shell")
Dim strMessage : strMessage = "[Inform]Dear User now there's happening something" &vbNewLine &"[Execute]msiexex /fup "
objShell.LogEvent 4, strMessage


PerformInUserContext.vbs
Dim objShell : objShell = CreateObject("Wscript.Shell")
Dim strMessage, arrEventEntry
Set objWMIService = GetObject("winmgmts:" & "{impersonationLevel=impersonate}!\\" & "." & "\root\cimv2")
Set colLoggedEvents = objWMIService.ExecQuery("Select * from Win32_NTLogEvent Where Logfile = 'Application' and SourceName='WSH' and EventCode=4 and Type='Information'")

For Each objEvent In colLoggedEvents
strMessage = objEvent.Message
Exit For
Next

arrEventEntry = Split(strMessage, vbNewLine)

For Each strLine In arrEventEntry
If InStr(strLine, "[Inform]") Then
MsgBox(Replace(strLine, "[Inform]", ""))
ElseIf InStr(strLine, "[Execute]") Then
objShell.Run(Replace(strLine, "[Execute]", ""), 1, False)
End If
Next

2/16/2011

Windows 7 SP1 Released

Windows 7 and Windows Server 2008 R2 Service Pack 1 is no available for download on MSDN

2/01/2011

vbScript - Get running environment

The following vbScript Function delivers the value true if the script runs within a task sequence environment. If not the functions delivers the value false. This function can be useful in scripts like wrapper and so on.


...
If RunningInTaskSequence = True Then
...

Function RunningInTaskSequence()
On Error Resume Next
Err.Clear
Dim objTSEnv : Set objTSEnv = CreateObject("Microsoft.SMS.TSEnvironment")
If Err Then
'Script does not run within the Task Sequence environment
RunningInTaskSequence = False
Else
'Script does run within the Task Sequence environment
RunningInTaskSequence = True
End If
On Error GoTo 0
End Function

Thanks to Mark Cochrane and Sherry Kissinger...

...for mention me in the new release of the RegKeyToMof v2.5 tool
Here's the associated blog entry

12/01/2010

Task Sequence Environment not available

Problem:
Your deploying a Script which shell use Task Sequence Variables with SCCM.
There seems to be a problem when creating the object for the Task Sequence Environment CreateObject("Microsoft.SMS.TSEnvironment").
You may also get the following error: Error 429 - ActiveX component can't create object.

Solution:
Make sure that your really running the script within a Task Sequence. Also check that you have created a Task Sequence Advertisement (Advertise Task Sequence).

9/27/2010

NULL Values in SCCM Hardware Inventory

I tried to capture the value of the registry key HKLM\SOFTWARE\YOURCOMPANY\Department from all systems independed of the OS architecture. I had the problem that the inventory delivered NULL Values in the database, although everything seemed to be configured correctly. The problem occurred very sporadic. Sometimes a system delivered a value and sometimes it didn’t.
Here’s my basic configuration.mof and sms_def.mof

configuration.mof
#pragma namespace ("\\\\.\\root\\cimv2")
#pragma deleteclass("CustomHINV86", NOFAIL)
[DYNPROPS]
class CustomHINV86
{
[key] string KeyName = "";
string Department;
};

[DYNPROPS]
instance of CustomHINV86
{
KeyName="CustomHINV";
[PropertyContext("localHKEY_LOCAL_MACHINE\\SOFTWARE\\YOURCOMPANYDepartment"),
Dynamic, Provider("RegPropProv")]
Department;
};

#pragma namespace ("\\\\.\\root\\cimv2")
#pragma deleteclass("CustomHINV64", NOFAIL)
[DYNPROPS]
class CustomHINV64
{
[key] string KeyName = "";
string Department;
};

[DYNPROPS]
instance of CustomHINV64
{
KeyName="CustomHINV";
[PropertyContext("localHKEY_LOCAL_MACHINE\\SOFTWARE\\YOURCOMPANYDepartment"),
Dynamic, Provider("RegPropProv")]
Department;
};



sms_def.mof
#pragma namespace ("\\\\.\\root\\cimv2\\SMS")
#pragma deleteclass("CustomHINV86", NOFAIL)
[SMS_Report(TRUE),
SMS_Group_Name("Your Custom HINV"),
SMS_Class_ID("YOURCOMPANYYour Custom HINV1.0"),
SMS_Context_1 ("__ProviderArchitecture=32uint32"),
SMS_Context_2 ("__RequiredArchitecture=trueboolean")]

Class CustomHINV86: SMS_Class_Template
{
[SMS_Report(TRUE),key] string KeyName;
[SMS_Report(TRUE)] String Department;
};

#pragma namespace ("\\\\.\\root\\cimv2\\SMS")
#pragma deleteclass("CustomHINV64", NOFAIL)
[SMS_Report(TRUE),
SMS_Group_Name("Your Custom HINV"),
SMS_Class_ID("YOURCOMPANYYour Custom HINV1.0"),
SMS_Context_1 ("__ProviderArchitecture=64uint32"),
SMS_Context_2 ("__RequiredArchitecture=trueboolean")]

Class CustomHINV64: SMS_Class_Template
{
[SMS_Report(TRUE),key] string KeyName;
[SMS_Report(TRUE)] String Department;
};


The result was the following entry in the SQL table
ResourceIDGroupIDRevisionIDAgentIDTimeStampDepartment0KeyName0
3313114.09.2010 16:59:19NULLCustomHINV


That’s the problem!
As the hardware inventory listed above is designed for both OS architecture, there should be created two entries in the table. I then compared those entries with some working one, from another customization and saw the differences.
The entries were overwritten, as the column KeyName0 is not unique. This also explained the sporadic behavior. Depending on which inventory entry (32bit or 64 bit) was written into the database in which order, the first one was overwritten.
The correct entries for a 64 bit system should look like this:
ResourceIDGroupIDRevisionIDAgentIDTimeStampDepartment0KeyName0
3313114.09.2010 16:59:19NULLCustomHINV86
3313114.09.2010 16:59:19MyValueCustomHINV64


So the result was to customize the configuration.mof file the get unique KeyName0 columns. Here it is:
#pragma namespace ("\\\\.\\root\\cimv2")
#pragma deleteclass("CustomHINV86", NOFAIL)
[DYNPROPS]
class CustomHINV86
{
[key] string KeyName = "";
string Department;
};

[DYNPROPS]
instance of CustomHINV86
{
KeyName="CustomHINV86";
[PropertyContext("localHKEY_LOCAL_MACHINE\\SOFTWARE\\YOURCOMPANYDepartment"),
Dynamic, Provider("RegPropProv")]
Department;
};


#pragma namespace ("\\\\.\\root\\cimv2")
#pragma deleteclass("CustomHINV64", NOFAIL)
[DYNPROPS]
class CustomHINV64
{
[key] string KeyName = "";
string Department;
};

[DYNPROPS]
instance of CustomHINV64
{
KeyName="CustomHINV64";
[PropertyContext("localHKEY_LOCAL_MACHINE\\SOFTWARE\\YOURCOMPANYDepartment"),
Dynamic, Provider("RegPropProv")]
Department;
};