WMI stuff... And Some Dirty Tricks!

pet - 27.06.2008

Win32_OperatingSystem.Win32Shutdown C# Story


Windows PowerShell is intended to be the system administrators' tool of the future. Since it's release it has been widely accepted, which is not a surprise given it's powerful features, especially it's ability to manipulate .Net objects natively.  I have also used it, but, truth be told, I don't like it very much. I guess I am used to VBSript verbosenes too much, and the whole PowerShell syntax smells like ... gasoline, sorry, like ... UNIX to me. There are probably other sysadmins that feel like me - luckily, Microsoft provides other options for those, namely Microsoft Visual Studio Express, which also provides access to .Net world natively, and it's free!***. You only need to download it, so I did, and having downloaded it, I have used it. Here is one of the outcomes of that usage:

 

using System;
using System.Diagnostics;
using System.Management;
using System.Runtime.InteropServices;


namespace WMITest
{

    // Win32_OperatingSystem.Win32Shutdown will accept
    // only these values for Flags parameter:

    public enum Flags
    {
        LogOff = 0,
        ShutDown = 1,
        Reboot = 2,
        ForcedLogOff = 4,
        ForcedShutdown = 5,
        ForcedReboot = 6,
        PowerOff = 8,
        ForcedPowerOff = 12
    }

    class Program
    {

        // This function attempts to execute
        // Win32_OperatingSystem.Win32Shutdown method
        // Machine is the remote computer NetBIOS name
        // or IP address

        static public int Shutdown(
            string Machine, string Username,
            string Password, Flags ShutdownType)
        {

            ConnectionOptions options =
                new ConnectionOptions();

            // Username and Password are not allowed
            // when connecting to the local computer
            // Local computer can be referred to with
            // a dot (.) or it's BIOS name

            if (Machine != "." || Machine != Environment.MachineName)
            {
                options.Username = Username;
                options.Password = Password;
            }

            // It looks like that EnablePrivileges
            // enables ALL Windows NT privileges
            // including SE_SHUTDOWN_NAME privilege
            // required to perform the shutdown operation.
            // This is not very selective, but I haven't
            // found a way to enable or disable individual
            // privileges. That doesn't mean that there isn't one.

            options.EnablePrivileges = true;

            ManagementScope scope =
                new ManagementScope(@"\\" + Machine
                + @"\root\cimv2", options);
           
            // I explicitly use ManagementScope.Connect()
            // because I don't want to procede if the
            // remote computer is unavailable.
            // If I don't use this and the remote machine
            // can not be reached, an exception will be
            // thrown the first time ManagementScope
            // is used in an operation that requires it
            // to be connected, ie. the
            // ManagementBaseObject inParams = ... line

            try
            {
                scope.Connect();
            }
            catch (COMException e)
            {
                return e.ErrorCode;
            }

            // First need to get the class definition
            // and it's Win32Shutdown method
            // input parameters object

            ManagementClass OSClass =
                new ManagementClass(scope,
                new ManagementPath(@"Win32_OperatingSystem"),
                null);

            ManagementBaseObject inParams =
            OSClass.Methods["Win32Shutdown"].InParameters;

            // Only set the Flags parameter.
            // The Reserved parameter defaults to 0
            // and is ignored.

            inParams["Flags"] = (int)ShutdownType;

            ManagementBaseObject outParams = null;

            // Win32_OperatingSystem.Win32Shutdown
            // is an instance INSTANCE! Argh!!! method
            // so in order to invoke it, we first
            // need to the actual instance
            // of Win32_OperatingSystem class
            // I use ManagementClass.GetInstances() for this
            // but there should be only one instance

            foreach (ManagementObject OSInstance
                in OSClass.GetInstances())
            {
                outParams = OSInstance.InvokeMethod(
                    "win32shutdown", inParams, null);
            }

            // outParams contains the method return value,
            // Zero indicates success
            // Any other value indicates an error.


            return Convert.ToInt32(
                outParams.Properties["ReturnValue"].Value);
        }

        // Time to test the whole thing

        public static void Main()
        {
            int RetVal = Shutdown(
                "aName", "Name", "Pwd", Flags.LogOff);

            Console.WriteLine(RetVal);
        }

    }
}

Win32_OperatingSystem.Win32Shutdown is an INSTANCE, not class method. I realised this obvious fact only after an hour of catching all kinds of exceptions. Gr!

OK, programming in Visual Studio feels, well, like programming. It IS different than throwing a few lines of VBScript and getting a result you can use, but there may be reasons to consider it an alternative to PowerShell: IDE, the Framework, IntelliSense, C# ... Hm ... C# is a pain, but a less of a pain than PowerShell, and even less of a pain than VB.Net (I never thaught I'd say that).

All  this said, VBScript is still alive and well for sysadmins to use - although It seems it was on life support for a while: they discontinued it's support in Visual Studio 2008, but were forced to put it back with Service Pack 1 (at least this is what goes around).

*** (A digression: Microsoft is shipping more and more free products and more and more Linux distributions are becoming commercial. What does that tell me? I haven't got a clue, it looks really weird...)

|| uros c., 27. jun 2008 21:55:32 || link || (0) komentara


sub - 21.06.2008

WMI.NET Provider Exception Handling HowTo

 

WMI.NET Provider Extensions will handle some exceptions for you, translating them into WMI error codes. Here they are:


System.OutOfMemoryException
   WBEM_E_OUT_OF_MEMORY
 
System.Security.SecurityException
   WBEM_E_ACCESS_DENIED
 
System.ArgumentException
   WBEM_E_INVALID_PARAMETER
 
System.ArgumentOutOfRangeException
   WBEM_E_INVALID_PARAMETER
 
System.InvalidOperationException
   WBEM_E_INVALID_OPERATION
 
System.Management.Instrumentation.InstanceNotFoundException
   WBEM_E_NOT_FOUND
 

For instance, if I take a simple class that represents a process:


using System;
using System.Collections;
using System.Diagnostics;
using System.Management.Instrumentation;

[assembly: WmiConfiguration(
    @"root\test",
    HostingModel =
    ManagementHostingModel.NetworkService)]

namespace WmiTest
{

    [System.ComponentModel.RunInstaller(true)]
    public class MyInstall : DefaultManagementInstaller
    {
    }

    [ManagementEntity(Name = "My_Process")]
    public class MyProcess
    {

        [ManagementBind]
        public MyProcess (int processId)
        {
            this.processId =
                Process.GetProcessById(processId).Id;
        }

        [ManagementKey]
        public int processId;
    }
}


make it a dll and publish it to WMI, I can use a script such as this to query the newly created class:


' VBScript source code

Set objWMI = GetObject _
("winmgmts:root\test")

Set objInstance = objWMI.Get _
("My_Process.ProcessId=172")

WScript.Echo objInstance.ProcessId


and the script will echo 172 to the Command Prompt. OK, so far so good. I want to do some more testing, so I provide the script with  random number that couldn't possibly represent a process handle:

 
' VBScript source code

Set objWMI = GetObject _
("winmgmts:root\test")

Set objInstance = objWMI.Get _
("My_Process.ProcessId=2147483647")

WScript.Echo objInstance.ProcessId


Now I get the following output:


C:\scripts\test.vbs(6, 1) SWbemServicesEx: Invalid parameter


This also works as expected, Process.GetProcessById() function throws a System.ArgumentException and it gets translated to WBEM_E_INVALID_PARAMETER error. OK, I take the things a little further (18,446,744,073,709,551,615 is the maximum value of ulong .Net data type):

 

' VBScript source code

Set objWMI = GetObject _
("winmgmts:root\test")

Set objInstance = objWMI.Get _
("My_Process.ProcessId=18446744073709551788")

WScript.Echo objInstance.ProcessId


And the output is again 172.


I remember this thing from C++ classes, it is called "wrapping around", but I thaught this was not supposed to happen in C#. OK, WMI.NET Extensions interacts with WMI COM API, so I pretend I am not surprised, go along with it and do another test:


' VBScript source code

Set objWMI = GetObject _
("winmgmts:root\test")

Set objInstance = objWMI.Get _
("My_Process.ProcessId=99999999999999999999999999999999")

WScript.Echo objInstance.ProcessId


This time I get the following error from WMI:


C:\scripts\test.vbs(6, 1) SWbemServicesEx: Invalid object path


Which is not even listed here as an arror that I could possibly get from WMI.NET Provider (So, I guess, the VBScript SWbemServices.Get call didn't even make it to .Net? ):

http://msdn.microsoft.com/en-us/library/cc268228.aspx


At this point I decide to throw something completely unexpected at WMI.NET Provider Extensions: a string


' VBScript source code

Set objWMI = GetObject _
("winmgmts:root\test")

Set objInstance = objWMI.Get _
("My_Process.ProcessId='Blah!'")

WScript.Echo objInstance.ProcessId


This one script crashes with this message showing in the Application Event Log:


Event Type: Error
Event Source: .NET Runtime
Event Category: None
Event ID: 1023
Date:  6/21/2008
Time:  7:59:00 PM
User:  N/A
Computer: ANAME
Description:
.NET Runtime version 2.0.50727.1433 - System.FormatException: Input string was not in a correct format.
   at System.Number.StringToNumber(String str, NumberStyles options, NumberBuffer& number, NumberFormatInfo info, Boolean parseDecimal)
   at System.Number.ParseInt32(String s, NumberStyles style, NumberFormatInfo info)
   at System.String.System.IConvertible.ToInt32(IFormatProvider provider)
   at System.Convert.ChangeType(Object value, Type conversionType, IFormatProvider provider)
   at System.Management.Instrumentation.WbemMarshal.TypeHelper.KnownTypeHandler.ConvertFromString(String s)
   at System.Management.Instrumentation.WbemMarshal.TypeHelper.KnownTypeHandler_String.ChangeType(Object o, Type t)
   at System.Management.Instrumentation.WbemMarshal.TypeHelper.ChangeType(Object o, Type t)
   at System.Management.Instrumentation.WbemMarshal.PrepareManagedObjectForProvider(Object& oValue, Type destType)
   at System.Management.Instrumentation.ConstructorInvoker.InvokeInfrastructureCtor(ObjectDictionary parameters, Int32 hresultForMissingProps, String sOperationRequested)
   at UMPProvider.Get(CallContext context, ICIMResultHandler resultHandler)
   at WmiNative.WbemProvider.WmiNative.IWbemServices.GetObjectAsync(String objectPath, Int32 flags, IWbemContext wbemContext, IWbemObjectSink wbemSink)
For more information, see Help and Support Center at
http://go.microsoft.com/fwlink/events.asp.


This, at last is too much for my inexperienced c# mind. How can I handle THIS? But, at least, one thing is obvious from this, that should have been obvious from the beginning: WMI treats WQL query strings as strings (hehe). Here is a sample that shows this:


' VBScript source code

Set objWMI = GetObject _
("winmgmts:root\test")

Set objInstance = objWMI.Get _
("My_Process.ProcessId='172'")

WScript.Echo objInstance.ProcessId


I provide a string where an int is expected and WMI swallows it returning the correct result. OK, so in the script that hung, somewhere in between the VBScript and WMI.NET Provider Extensions there is an attempt to convert 'Blah!' to an integer value and as a result a System.FormatException is thrown. But how do I handle THAT?


 

|| uros c., 21. jun 2008 22:05:56 || link || (0) komentara


sre - 18.06.2008

Translating .Net Exceptions Into WMI Errors

WMI Provider Extensions will handle some exceptions for you. This means that these exceptions will be translated to WMI error codes and passed to the client. All other exceptions will crash the provider and an entry will be created in the Application event log. Here is a simple class with only three properties and the ManagementBind method (ManagementEnumerator is ommited):


using System;
using System.Collections;
using System.IO;
using System.Management.Instrumentation;

[assembly: WmiConfiguration(
    @"root\test",
    HostingModel = ManagementHostingModel.NetworkService)]

namespace WmiTest
{

    [System.ComponentModel.RunInstaller(true)]
    public class MyInstall : DefaultManagementInstaller
    {
    }

    [ManagementEntity(Name = "My_DataFile")]
    public class MyDataFile
    {

        private FileInfo objFile;

        public MyDataFile(FileInfo objFile)
        {
            this.objFile = objFile;
        }

        [ManagementKey]
        public string FullName
        {
            get
            {
                return objFile.FullName;
            }
        }

        [ManagementProbe]
        public DateTime CreationDate
        {
            get
            {
                return objFile.CreationTime;
            }
        }


        [ManagementProbe]
        public long Length
        {
            get
            {
                return objFile.Length;
            }
        }

        [ManagementBind]
        static public MyDataFile GetInstance(string FullName)
        {
            FileInfo objFile = new FileInfo(FullName);
            return new MyDataFile(objFile);
        }
    }
}


When this class is put into the repository, you can query it with a VBScript such as this one:


' VBScript source code

Set objWMI = GetObject _
("winmgmts:root\test")

Set objInstance = objWMI.Get _
("My_DataFile.FullName='" _
& WScript.ScriptFullName & "'")

WScript.Echo objInstance.FullName, _
objInstance.Length


and the script output will look something like this:


C:\scripts\test.vbs 226


but if you try to bind to an instance that doesn't exist:


' VBScript source code

Set objWMI = GetObject _
("winmgmts:root\test")

Set objInstance = objWMI.Get _
("My_DataFile.FullName='c:\scripts\bogus.txt'")

WScript.Echo objInstance.FullName, _
objInstance.Length


The script will not report any error. Instead, it will hang, and a message like this will be recorded in the Application event log:


Event Type: Error
Event Source: .NET Runtime
Event Category: None
Event ID: 1023
Date:  6/18/2008
Time:  8:43:12 PM
User:  N/A
Computer: AName
Description:
.NET Runtime version 2.0.50727.1433 - Unexpected exception thrown from the provider:

System.IO.FileNotFoundException: Could not find file 'bogus.txt'.
File name: 'bogus.txt'
   at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
   at System.IO.FileInfo.get_Length()
   at WmiTest.MyDataFile.get_Length()
For more information, see Help and Support Center at

http://go.microsoft.com/fwlink/events.asp.


But if you change the ManagementBind method to throw an InstanceNotFound exception:


        [ManagementBind]
        static public MyDataFile GetInstance(string FullName)
        {
            FileInfo objFile = new FileInfo(FullName);
            if (objFile.Exists)
            {
                return new MyDataFile(objFile);
            }
            else
            {
                throw new InstanceNotFoundException();
            }
        }


the above script will return a 'normal' WMI error:


C:\scripts\test.vbs(6, 1) SWbemServicesEx: Not found


Or, if you include the traditional VBScript error handling:


' VBScript source code

Set objWMI = GetObject _
("winmgmts:root\test")

On Error Resume Next

Set objInstance = objWMI.Get _
("My_DataFile.FullName='" _
& "bogus.txt" & "'")

If Err.number Then
   WScript.Echo Hex(Err.number),_
   Err.Description
End If

WScript.Echo objInstance.FullName, _
objInstance.Length


you can see that InstanceNotFound exception is transleted into WMI wbemErrNotFound error constant:


80041002 Not found


So ... my daily mantra at the moment is: gotta learn .Net exceptions ... gotta learn .Net exceptions ... etc
 

|| uros c., 18. jun 2008 23:41:55 || link || (1) komentara


Pozdrav, u prolazu!!!!
MMMila, u 19. jun 2008 11:05:57