Applying a snapshot in Hyper-V WMI V2 from C# - c#

I am trying to replicate the following PowerShell in C#:
# Details from here are not particularly important but needed for full sample
$vms = gwmi -namespace "root\virtualization\v2" Msvm_ComputerSystem
$vm = $vms[3]
$snapshot = ($vm.GetRelated("Msvm_VirtualSystemSettingData") | Where { $_.ElementName -eq "SnapshotName" })[0]
# end unimportant
$VMSS = Get-WMiObject -class Msvm_VirtualSystemSnapshotService -namespace root\virtualization\v2
$VMSS.ApplySnapshot($snapshot, $null)
This code works fine - the snapshot applies as expected.
I have no problem getting the Msvm_VirtualSystemSettingData instance or a Msvm_VirtualSystemSnapshostService instance in C#. However, I can't seem to get the call to ApplySnapshot right - no matter what I give it I get an InvalidOperationException. I'm using generated WMI code from Visual Studio for the call:
public uint ApplySnapshot(ref System.Management.ManagementPath Job, System.Management.ManagementPath Snapshot) {
if ((isEmbedded == false)) {
System.Management.ManagementBaseObject inParams = null;
inParams = PrivateLateBoundObject.GetMethodParameters("ApplySnapshot");
// following line has been through variations as well with no change -
// commenting it out, setting to null
inParams["Job"] = ((System.Management.ManagementPath)(Job)).Path;
inParams["Snapshot"] = ((System.Management.ManagementPath)(Snapshot)).Path;
System.Management.ManagementBaseObject outParams = PrivateLateBoundObject.InvokeMethod("ApplySnapshot", inParams, null);
Job = ((System.Management.ManagementPath)(outParams.Properties["Job"].Value));
return System.Convert.ToUInt32(outParams.Properties["ReturnValue"].Value);
}
else {
return System.Convert.ToUInt32(0);
}
}
I'm not sure what to pass for the Job parameter either, since we get a job back - it's very unusual to have a ref for that instead of an out but I've tried a bunch of different variations around that (including setting the parameter to null and not setting it at all) with no luck. I've also tried setting inParams[Job] to null with no luck for that either.
What should I change to make this work?

I believe your problem is that you are passing in the job to start with when it is an outbound parameter. That parameter will return from the invocation. Something like..
ManagementBaseObject inParams = null;
inParams = PrivateLateBoundObject.GetMethodParameters("ApplySnapshot");
inParams["Snapshot"] = ((System.Management.ManagementPath)(Snapshot)).Path;
ManagementBaseObject outParams = PrivateLateBoundObject.InvokeMethod("ApplySnapshot", inParams, null);
// i left this as i assume this is vs generated though this isn't how i would normally
// get my jobs back.
Job = ((ManagementPath)(outParams.Properties["Job"].Value));
return Convert.ToUInt32(outParams.Properties["ReturnValue"].Value);

Related

C# SCCM - Client Action

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;

Why is it not possible to invoke Powershell as a regular process in C#

Wanted to know if there is a reason that Powershell needs a special library (in System.Management.Automation NS) to be invoked from C# code? I have this code:
Process p = new Process();
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
string trackerPath = "C:\\Users\\bernam\\Downloads\\test_scripts\\test_scripts\\Mindaugas_Scripts\\test.ps1";
p.StartInfo.FileName = "Powershell.exe";
p.StartInfo.Arguments = " -file " + trackerPath;
Console.WriteLine(p.StartInfo.FileName + p.StartInfo.Arguments);
p.Start();
string output = p.StandardOutput.ReadToEnd();
And it does not seem to be working - output is not returned back. However all these scenarios work fine (produce desired output):
p.StartInfo.FileName = "Powershell.exe"; p.StartInfo.Arguments = "get-service"; - works fine.
Invoking the PS script from the command line works fine as well (invocation from CMD):
>Powershell.exe -file "C:\Users\<shortened>\test.ps1"
1
2
The powershell code inside the script:
1..10 | % { Write-Host $_ ; sleep -m 500}
I know that there is a recommendation to use PowerShell class in System.Management.Automation namespace. What is interesting to me - is why? And is it possible to use PS without that Class? Maybe my code is simply wrong?
Yes this is certainly possible.
Try replacing Write-Host with Write-Output in the script you are calling.
Write-Host does not write to the standard streams, it writes to a console host. If you are not running a console host (cmd/powershell console), the output will just disappear. In general it is best to avoid using Write-Host all together.
The reason most people recommend using the System.Management.Automation method, is it simplifies many interactions that you may wish to use with powershell, rather than trying to parse the returns from powershell.exe, there are however valid reasons for calling the exe directly, for example if you are using .net core, which doesn't currently fully support System.Management.Automation.
You should invoke PS scripts from System.Management.Automation NS, because then you can work with results and exceptions having type-safe environment.
EDIT also, you can use asynchronous execution or you can execute PS scripts on a Remote server. Generally, you have much more possibilities using that library.
You can take a look at my example below.
string script = ""; // PS script content
List<ScriptParameter> ExecParamList; // parameters
var result = new ExecPSResult(); // Class with list of outputs and errors
using (var powerShellInstance = PowerShell.Create())
{
powerShellInstance.AddScript(script);
foreach (var execParamModel in ExecParamList)
{
powerShellInstance.AddParameter(execParamModel.ParamName,
execParamModel.ParamValue ?? "$null");
}
var psOutput = powerShellInstance.Invoke();
result.Errors =
powerShellInstance.Streams.Error.Select(e =>
ExecException.MakeFromException(e.Exception) // just make models for exeptions
).ToList();
result.OutputItems =
psOutput.Where(
outputItem =>
outputItem != null &&
outputItem.TypeNames[0] != "System.ServiceProcess.ServiceController")
.Select(e => new ExecOutput
{
ObjectTypeFullName = e.BaseObject.GetType().FullName,
ObjectValue = e.BaseObject //This is typeof(Object)
}).ToList();
}
return result;

Visual-SVN Server C# code not properly adding user permissions (version 2.7.2)

Story time! One of our more disgruntled employees decided to upgrade visual svn and modify our web interface a day before his last day. We had the old authentication set up, and it was all working fine. Give this particular applications footprint in the grand scheme we followed the 'if it ain't broke, don't fix it' mantra.
It wasn't broke, and he fixed it...
SO here we are. I found This Question regarding interfacing with Visual SVN with C#, and it looks like he had just copied and pasted the code verbatim from there.
The interface we have is very simple. There is an input box that the user types in the name of there desired repo. Below that is a text area where he/she can add users to have access to the repo. The user lookup is done based off of email address and it hits our active directory. The end result of this is I have the name of the repo I need to create, and the users/SIDs of the people I need to give read/write access to.
Testing this code he pasted in, it seems like the repositories are getting created fine (they show up when I log into the server as an admin). Here is the repo creation code:
static public bool CreateRepository(repository r)
{
ManagementClass repoClass = new ManagementClass("root\\VisualSVN", "VisualSVN_Repository", null);
// Obtain in-parameters for the method
ManagementBaseObject inParams = repoClass.GetMethodParameters("Create");
// Add the input parameters.
inParams["Name"] = r.name;
// Execute the method and obtain the return values.
ManagementBaseObject outParams =
repoClass.InvokeMethod("Create", inParams, null);
return true;
}
'repository r' in the method parameters, repository is a class with the following properties:
private int _id;
private string _name;
private System.Nullable<System.DateTime> _deleteAfter;
private EntitySet<UserRepositoryRight> _UserRepositoryRights;
with all the public getters and setters you would expect from a linq to sql generated file.
UserRepositoryRight is a table that holds the one (repo) to many (users) relationships.
Like I said, I think this code is fine, since I am seeing the repositories being created.
The next copypasta code is the UpdatePermissions method
static public void UpdatePermissions(string sid, string repository, AccessLevel level, bool isAdmin = false)
{
//Update SVN
ManagementClass userClass = new ManagementClass("root\\VisualSVN", "VisualSVN_WindowsAccount", null);
ManagementClass permClass = new ManagementClass("root\\VisualSVN", "VisualSVN_PermissionEntry", null);
ManagementClass repoClass = new ManagementClass("root\\VisualSVN", "VisualSVN_Repository", null);
ManagementObject userObject = userClass.CreateInstance();
userObject.SetPropertyValue("SID", sid);
ManagementObject permObject = permClass.CreateInstance();
permObject.SetPropertyValue("Account", userObject);
permObject.SetPropertyValue("AccessLevel", level);
ManagementObject repoObject = repoClass.CreateInstance();
repoObject.SetPropertyValue("Name", repository);
ManagementBaseObject inParams =
repoClass.GetMethodParameters("SetSecurity");
inParams["Path"] = "/trunk";
inParams["Permissions"] = new object[] { permObject };
ManagementBaseObject outParams =
repoObject.InvokeMethod("SetSecurity", inParams, null);
//Update in DB
var db = new DataMapSVNServiceDataContext();
if (level == AccessLevel.NoAccess) //If we are removing the user
{
var output = (db.repositories.Single(r => r.name == repository)).UserRepositoryRights.Single(u => u.User.Sid == sid);
if (output.isAdmin != null && !((bool)output.isAdmin)) //making sure DB owner isn't ever removed
db.UserRepositoryRights.DeleteOnSubmit(output);
}
if (level == AccessLevel.ReadWrite) //if we are adding the user
{
var add = new UserRepositoryRight
{
isAdmin = isAdmin,
User = db.Users.Single(u => u.Sid == sid),
repository = db.repositories.Single(r => r.name == repository)
};
db.UserRepositoryRights.InsertOnSubmit(add);
}
db.SubmitChanges();
}
Here everything looks ok, but it does not seem to be carrying over to the repo and adding the user to have ReadWrite (key value is 2) permissions on the created repo. The tail end of the method just saves the data to our websites databases to allow us to interface with it.
So, the root problem is if I create a repo via the web interface here, I get a 403 Forbidden error when trying to access it, but NO errors when creating and deleting it. Can anyone point me in the right direction here?
You should use VisualSVN Server PowerShell cmdlets instead of using the server's WMI provider. The WMI provider of VisualSVN Server can be considered as an internal API. It is not documented and is not intended to be used to build custom applications.
Old answer
If you don't get any errors when running the code, I guess that the access rule is set on path <repo>/trunk which simply does not exist in the youngest revision. You can try the script on some fresh testing repository which contains "/trunk" in it's root. Otherwise, you can simply change change the code string inParams["Path"] = "/trunk"; to inParams["Path"] = "/";.
VisualSVN Server allows you to setup path-based authorization rules on items (files and folders) that don't exist in youngest revision because these items can exist in earlier and newer revisions.

Enabling filestream with WMI and C#

I have need to enable FILESTREAM on a SQL Server through a program. According to documentation I have found, this can be done through WMI with the following code:
set fsInstance = GetObject("WINMGMTS:\\" & MachineName & "\root\Microsoft\SqlServer\ComputerManagement10:FilestreamSettings='" & InstanceName & "'")
Set method = fsInstance.Methods_("EnableFilestream")
Set inParam = method.inParameters.SpawnInstance_()
inParam.AccessLevel = TargetLevel
inParam.ShareName = ShareName
Set outParam = fsInstance.ExecMethod_("EnableFilestream", inParam)
Converting this to C#, I get the following:
ManagementClass mc = new ManagementClass(new ManagementScope(#"\\.\root\Microsoft\SqlServer\ComputerManagement10"),
new ManagementPath("FilestreamSettings"),
new ObjectGetOptions(new ManagementNamedValueCollection() {
{"InstanceName", "MSSQLSERVER"}
}));
ManagementBaseObject inParams = mc.GetMethodParameters("EnableFilestream");
inParams["AccessLevel"] = 1;
inParams["ShareName"] = "ALLIANCE";
mc.InvokeMethod("EnableFilestream", inParams, null);
Whenever I try this, though, it throws an exception on InvokeMethod. The message is "Invalid method parameter(s)". I have attempted to remove the two lines where I set AccessLevel and ShareName and run it that way, but get the same exception. What am I doing wrong here?

How to add a DNS alias programmatically?

I want to create an alias record in Microsoft's DNS server to point AliasA to ComputerA. How can I do this programmatically?
I used WMI to do this, found an example on the web, and this is what it looked like.
private ManagementScope _session = null;
public ManagementPath CreateCNameRecord(string DnsServerName, string ContainerName, string OwnerName, string PrimaryName)
{
_session = new ManagementScope("\\\\" + DnsServerName+ "\\root\\MicrosoftDNS", con);
_session.Connect();
ManagementClass zoneObj = new ManagementClass(_session, new ManagementPath("MicrosoftDNS_CNAMEType"), null);
ManagementBaseObject inParams = zoneObj.GetMethodParameters("CreateInstanceFromPropertyData");
inParams["DnsServerName"] = ((System.String)(DnsServerName));
inParams["ContainerName"] = ((System.String)(ContainerName));
inParams["OwnerName"] = ((System.String)(OwnerName));
inParams["PrimaryName"] = ((System.String)(PrimaryName));
ManagementBaseObject outParams = zoneObj.InvokeMethod("CreateInstanceFromPropertyData", inParams, null);
if ((outParams.Properties["RR"] != null))
{
return new ManagementPath(outParams["RR"].ToString());
}
return null;
}
I don't think .NET has anything to provide access to these (all I can find in a bit of quick searching is references to proprietary libraries, controls, etc.), so you'll probably have to use the Win32 API via P/Invoke (though another possibility would be to do the job via WMI).
You'd start with DnsAcquireContextHandle, then (probably) DnsQuery to get a current record set, modify its contents to add your new alias, DnsReplaceRecordSet to have the DNS server use the new set of records, and finally DnsReleaseContextHandle to shut things down.
Of course, you'll need the right permissions on the server or none of this will work at all.

Categories