I've seen many ways in powershell to force a computer to do a client action from the configuration manager.
Where I do work, it is not possible because we can't invoke commands on distant computer because it is blocked and the senior IT do not want to unlock it.
I did find a library in c# that allow me to do some action in sccm :
AdminUI.SmsTraceListener.dll
AdminUI.WqlQueryEngine.dll
I can add/remove computer to a collections, make queries and get the data, but I didn't find the way to force a computer to make an action from the configuration manager.
Is there someone here that knows if it is possible and how?
Thanks.
Edit 1: While searching in the MSDN documentation, I did find the TriggerSchedule Method in Class SMS_Client but I don't find the way to use it correctly. I think it might be the way to go, but i'm still stuck on this.
It is possible to trigger an Machine Policy Update via TriggerSchedule like this
ManagementScope scope = new ManagementScope(#"\\.\root\ccm");
ManagementClass cls = new ManagementClass(scope.Path.Path, "SMS_Client", null);
ManagementBaseObject inParams = cls.GetMethodParameters("TriggerSchedule");
inParams["sScheduleID"] = "{00000000-0000-0000-0000-000000000021}";
ManagementBaseObject outMPParams = cls.InvokeMethod("TriggerSchedule", inParams, null);
You already found the other Parameters for the sScheduleID in the link you posted. This uses standard WMI. With WqlQueryEngine you would get access to some WMI wrappers that can basically do the same thing. I do not see many advantages however.
Using the scope like this
\\.\root\ccm
makes the whole thing only work locally which is what you want if I understood you correctly. Otherwise replacing the . With a hostname or IP would make it work remotely. Only thing I found a bit strange is that it needs administrative rights, which should in theory not be necessary for a policy update request.
if someone is having the issue that nothing is happening, it is because WMI required higher rights. To leave triggering the actions also by the user, I switched to use the CPApplet:
TriggerSccmActions("Request & Evaluate", true);
private static List<string> TriggerSccmActions(string stringActions, bool boolContains)
{
List<string> actionName = new List<string>();
try {
const string ProgID = "CPApplet.CPAppletMgr";
Type foo = Type.GetTypeFromProgID(ProgID);
dynamic COMobject = Activator.CreateInstance(foo);
var oClientActions = COMobject.GetClientActions;
foreach (var oClientAction in oClientActions)
{
if (oClientAction.Name.ToString().Contains(stringActions) && boolContains)
{
var result = oClientAction.PerformAction();
actionName.Add(oClientAction.Name.ToString());
}
else if (!(oClientAction.Name.ToString().Contains(stringActions)) && !(boolContains))
{
var result = oClientAction.PerformAction();
actionName.Add(oClientAction.Name.ToString());
}
}
} catch(Exception e)
{
actionName.Add("Error: " + e.Message.ToString());
}
return actionName;
}
For me, EvaluateMachinePolicy Method in Class SMS_Client class worked. Here is the code:
public static void RefreshMachinePolicy(string machineName)
{
ManagementScope scope = new ManagementScope(string.Format(#"\\{0}\root\ccm", machineName));
ManagementClass cls = new ManagementClass(scope.Path.Path, "SMS_Client", null);
ManagementBaseObject inParams = cls.GetMethodParameters("EvaluateMachinePolicy");
ManagementBaseObject outMPParams = cls.InvokeMethod("EvaluateMachinePolicy", inParams, null);
Console.WriteLine("Policy refreshed successfully by EvaluateMachinePolicy method");
}
Here is the MSDN link for method details. Please include below namespace at the top of your source code file:
using System.Management;
I'm researching what I need to build an Active Directory search tool which ultimately will be used in a C# ASP.NET web app.
I know very little about AD (and don't particularly want to know any more than necessary) so I've asked our tech ops to set up a dummy instance on a server. This they've done giving it the domain dummy.local
I now need to work out my LDAP connection string. Here I'm completely stuck. I'm using a user account which is a member of Domain Admins. After loads of hunting round the web I've tried all sorts of things to work out the various components of the LDAP connection string. For example, if I run the following in cmd.exe for that domain admin user on the server...
dsquery user -samid "username" | dsget user -memberof -expand
...this gives me the following information:
"CN=Domain Admins,CN=Users,DC=dummy,DC=local"
"CN=Remote Desktop Users,CN=Builtin,DC=dummy,DC=local"
"CN=Users,CN=Builtin,DC=dummy,DC=local"
"CN=Administrators,CN=Builtin,DC=dummy,DC=local"
"CN=Domain Users,CN=Users,DC=dummy,DC=local"
"CN=Denied RODC Password Replication Group,CN=Users,DC=dummy,DC=local"
I've also run the following in a C# console app...
using (var context = new PrincipalContext(ContextType.Domain))
using (var comp = ComputerPrincipal.FindByIdentity(context, Environment.MachineName))
{
Console.WriteLine(String.Join(",", comp.DistinguishedName.Split(',').SkipWhile(s => !s.StartsWith("OU=")).ToArray()));
}
...and this gives me the following:
OU=Domain Controllers,DC=dummy,DC=local
I thought I therefore had all the properties I needed to build my LDAP connection string. I ended up with this:
LDAP://COMSECWEBDEV.dummy.local/ou=Domain Controllers,/cn=Domain Admins,/cn=Users,/dc=dummy,/dc=local
I've tried using this connection string, with the username and password of the domain admin which I know is correct, but everything I try always gives me the same error:
System.Runtime.InteropServices.COMException (0x80005000): Unknown error (0x80005000)
at System.DirectoryServices.DirectoryEntry.Bind(Boolean throwIfFail)
at System.DirectoryServices.DirectoryEntry.Bind()
at System.DirectoryServices.DirectoryEntry.get_AdsObject()
at System.DirectoryServices.DirectorySearcher.FindAll(Boolean findMoreThanOne)
at System.DirectoryServices.DirectorySearcher.FindOne()
Since this error gives me no detail I have no idea what I'm doing wrong. I'm sure I'm just not getting the connection string right, but I've no idea how to work out the correct string.
For completeness, here is the console code which I'm testing with:
static void Main(string[] args)
{
var connString = ConfigurationSettings.AppSettings["lc"];
var username = ConfigurationSettings.AppSettings["lu"];
var password = ConfigurationSettings.AppSettings["lpw"];
using (DirectoryEntry de = new DirectoryEntry(connString, username, password))
{
DirectorySearcher search = new DirectorySearcher(de);
search.PageSize = 1001;// To Pull up more than 100 records.
DirectorySearcher directorySearcher = new DirectorySearcher(de);
directorySearcher.Filter = string.Format("(&(objectClass=user)(objectCategory=user) (sAMAccountName={0}))", username);
directorySearcher.PropertiesToLoad.Add("msRTCSIP-PrimaryUserAddress");
try
{
var result = directorySearcher.FindOne();
var found = false;
if (result != null)
{
if (result.Properties["msRTCSIP-PrimaryUserAddress"] != null)
{
found = true;
Console.WriteLine("Found: " + result.Properties["msRTCSIP-PrimaryUserAddress"][0]);
}
}
if (!found)
{
Console.WriteLine("Sad face");
}
}
catch (Exception x)
{
Console.WriteLine(x.Message);
}
Console.WriteLine("------------");
}
}
I was trying to figure out how to properly format a LDAP connection string last week, and found this entry over on serverfault:
How can I figure out my LDAP connection string?
I noticed you had a "/" between each OU or DC entry - I didn't include those in mine, and I don't see them included in the above example either.
I'm far from an expert (obviously) but just figured I would throw it out there.
I can find all sorts of stuff on how to program for DCOM, but practically nothing on how to set/check the security programmatically.
I'm not trying to recreate dcomcnfg, but if I knew how to reproduce all the functionality of dcomcnfg in C# (preferred, or VB.net) then my goal is in sight.
I can't seem to be able to find any good resource on this, no open source API's or even quick examples of how to do each step. Even here DCOM or dcomcnfg returns few results and none really about how to set/verify/list security.
If anybody has some pointers to an open API or some examples I would appreciate it.
The answer posted by Daniel was HUGELY helpful. Thank you so much, Daniel!
An issue with Microsoft's documentation is that they indicate that the registry values contain an ACL in binary form. So, for instance, if you were trying to set the machine's default access (rather than per-process), you would be accessing registry key HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Ole\DefaultAccessPermission. However, in my initial attempts to access this key using the System.Security.AccessControl.RawACL class were failing.
As Daniel's code indicate's the value is not actually an ACL, but really is a SecurityDescriptor with the ACL in it.
So, even though I know this post is old, I'm going to post my solution for checking and setting the security settings and adding NetworkService for Default local access. Of course, you could take this and make it better I'm sure, but to get started you would simply need to change the key and the access mask.
static class ComACLRights{
public const int COM_RIGHTS_EXECUTE= 1;
public const int COM_RIGHTS_EXECUTE_LOCAL = 2;
public const int COM_RIGHTS_EXECUTE_REMOTE = 4;
public const int COM_RIGHTS_ACTIVATE_LOCAL = 8;
public const int COM_RIGHTS_ACTIVATE_REMOTE = 16;
}
class Program
{
static void Main(string[] args)
{
var value = Registry.GetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Ole", "DefaultAccessPermission", null);
RawSecurityDescriptor sd;
RawAcl acl;
if (value == null)
{
System.Console.WriteLine("Default Access Permission key has not been created yet");
sd = new RawSecurityDescriptor("");
}else{
sd = new RawSecurityDescriptor(value as byte[], 0);
}
acl = sd.DiscretionaryAcl;
bool found = false;
foreach (CommonAce ca in acl)
{
if (ca.SecurityIdentifier.IsWellKnown(WellKnownSidType.NetworkServiceSid))
{
//ensure local access is set
ca.AccessMask |= ComACLRights.COM_RIGHTS_EXECUTE | ComACLRights.COM_RIGHTS_EXECUTE_LOCAL | ComACLRights.COM_RIGHTS_ACTIVATE_LOCAL; //set local access. Always set execute
found = true;
break;
}
}
if(!found){
//Network Service was not found. Add it to the ACL
SecurityIdentifier si = new SecurityIdentifier(
WellKnownSidType.NetworkServiceSid, null);
CommonAce ca = new CommonAce(
AceFlags.None,
AceQualifier.AccessAllowed,
ComACLRights.COM_RIGHTS_EXECUTE | ComACLRights.COM_RIGHTS_EXECUTE_LOCAL | ComACLRights.COM_RIGHTS_ACTIVATE_LOCAL,
si,
false,
null);
acl.InsertAce(acl.Count, ca);
}
//re-set the ACL
sd.DiscretionaryAcl = acl;
byte[] binaryform = new byte[sd.BinaryLength];
sd.GetBinaryForm(binaryform, 0);
Registry.SetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Ole", "DefaultAccessPermission", binaryform, RegistryValueKind.Binary);
}
}
Facing similar circumstances (configuring DCOM security from an MSI) I managed to create a solution that does what I want by altering registry key values in HKEY_CLASSES_ROOT\AppID{APP-GUID-GOES-HERE}. Thanks to Arnout's answer for setting me on the right path.
In specific, I created a method to edit the security permissions for DCOM objects, which are stored in the LaunchPermission and AccessPermission registry key values. These are serialized security descriptors, which you can access by passing the binary data through RawSecurityDescriptor. This class simplifies a lot of the details in a delicious .NET-y fashion, but you still have to attend to all the logical details regarding Windows ACL, and you have to make sure to write the security descriptor back to the registry by using RawSecurityDescriptor.GetBinaryForm.
The method I created was called EditOrCreateACE. This method will either edit an existing ACE for an account, or insert a new one, and ensure that the access mask has the passed flags set. I attach it here as an example, this is by no means any authority on how to deal with it, since I know very little of Windows ACL stuff still:
// These are constants for the access mask on LaunchPermission.
// I'm unsure of the exact constants for AccessPermission
private const int COM_RIGHTS_EXECUTE = 1;
private const int COM_RIGHTS_EXECUTE_LOCAL = 2;
private const int COM_RIGHTS_EXECUTE_REMOTE = 4;
private const int COM_RIGHTS_ACTIVATE_LOCAL = 8;
private const int COM_RIGHTS_ACTIVATE_REMOTE = 16;
void EditOrCreateACE(string keyname, string valuename,
string accountname, int mask)
{
// Get security descriptor from registry
byte[] keyval = (byte[]) Registry.GetValue(keyname, valuename,
new byte[] { });
RawSecurityDescriptor sd;
if (keyval.Length > 0) {
sd = new RawSecurityDescriptor(keyval, 0);
} else {
sd = InitializeEmptySecurityDescriptor();
}
RawAcl acl = sd.DiscretionaryAcl;
CommonAce accountACE = null;
// Look for the account in the ACL
int i = 0;
foreach (GenericAce ace in acl) {
if (ace.AceType == AceType.AccessAllowed) {
CommonAce c_ace = ace as CommonAce;
NTAccount account =
c_ace.SecurityIdentifier.Translate(typeof(NTAccount))
as NTAccount;
if (account.Value.Contains(accountname)) {
accountACE = c_ace;
}
i++;
}
}
// If no ACE found for the given account, insert a new one at the end
// of the ACL, otherwise just set the mask
if (accountACE == null) {
SecurityIdentifier ns_account =
(new NTAccount(accountname)).Translate(typeof(SecurityIdentifier))
as SecurityIdentifier;
CommonAce ns = new CommonAce(AceFlags.None, AceQualifier.AccessAllowed,
mask, ns_account, false, null);
acl.InsertAce(acl.Count, ns);
} else {
accountACE.AccessMask |= mask;
}
// Write security descriptor back to registry
byte[] binarySd = new byte[sd.BinaryLength];
sd.GetBinaryForm(binarySd, 0);
Registry.SetValue(keyname, valuename, binarySd);
}
private static RawSecurityDescriptor InitializeEmptySecurityDescriptor()
{
var localSystem =
new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null);
var new_sd =
new RawSecurityDescriptor(ControlFlags.DiscretionaryAclPresent,
localSystem, localSystem, null,
new RawAcl(GenericAcl.AclRevision, 1));
return new_sd;
}
Do note that this code is by no means perfect. If the entire registry key value for these ACL is missing in the registry, the ACL that is synthesized will ONLY grant access to the passed account and nothing else. I'm also sure there are lots of error conditions I've not handled properly and details I've glossed over. Again, it's an example of how to deal with DCOM ACL in .NET.
This information is stored in HKCR\AppID\{Your-AppID}\LaunchPermission and AccessPermission. These are REG_BINARY values containing serialized security descriptors. No idea whether there's anything providing convenient access to those from .NET...
More info on MSDN.
I couldn't find any .NET way of doing this - you can use the MS command line utility DCOMPerm (also here) which is part of the SDK.
I found this solution working:
public static void SetUp()
{
SetCOMSercurityAccess("DefaultAccessPermission");
SetCOMSercurityAccess("DefaultLaunchPermission");
}
private static void SetCOMSercurityAccess(string regKey)
{
//This is the magic permission!
byte[] binaryform = new string[]
{
"01","00","04","80","80","00","00","00","90","00","00","00","00","00","00","00","14","00","00","00","02","00","6c","00","04",
"00","00","00","00","00","14","00","1f","00","00","00","01","01","00","00","00","00","00","05","12","00","00","00","00","00",
"24","00","0b","00","00","00","01","05","00","00","00","00","00","05","15","00","00","00","a3","53","d8","c8","94","bd","63",
"84","88","bf","fa","cf","a7","2b","00","00","00","00","18","00","1f","00","00","00","01","02","00","00","00","00","00","05",
"20","00","00","00","20","02","00","00","00","00","14","00","1f","00","00","00","01","01","00","00","00","00","00","05","04",
"00","00","00","01","02","00","00","00","00","00","05","20","00","00","00","20","02","00","00","01","02","00","00","00","00",
"00","05","20","00","00","00","20","02","00","00"
}.Select(o=> Convert.ToByte(o,16)).ToArray();
Registry.SetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Ole", regKey, binaryform, RegistryValueKind.Binary);
}
In case it help others...