I did already show it here, WMI viewer script for MSH (GUI), but here you can see it Shows the relations created so you can walk them like this, also you can sort the Tables:
From User
To Computer :
# DataSet Viewer Example,
# $DS should already be filled # /\/\o\/\/ 2005 # http://mow001.blogspot.com
I think this is a nice way to look at the result of your Data combining done in Monad
If you got the data in the DataSet from different sources, don't forget you can save a dataset to XML for later use, see also : Using ADO to get data from Excel to Dataset in Monad. (If you do this for a DataTable take care that you have to save it with Schema to load it back in a DataTable Object, also you have to Name the DataTable before Saving.
Also you might want to look at the DataView Class to Filter the Data in the DataTable you would almost forget your still in a Command Shell
PS if you want to go back from computer to user just add another relation
# Add a relation back from Computer to User # Watch out ! child / Parent Switched Using Variables Of last Post ! $rel = new-object System.Data.DataRelation("ComputerToUser",$child,$parent,$false) $ds.Relations.add($rel)
After the Posts about getting data from Excel and SQL, and working with the DataTables and DataSets from MSH.
Now something about Relations between DataTables, it's also possible to "Join" the dataTables you have in Monad.
Let say you have the following Situation,
You have 2 DataTables :
One with Computers and one with users. To Create this situation, you can past the following Code into you MSH console (or make it a script first by saving to a file) :
$dt = new system.data.dataTable $dt.TableName = "tblComputer"
This is just to create the 2 datatables for the Example, you can also get them from another source as SQL server , MsAcsess, Excel etc. (as long as you can do ODBC) as explained in former posts, since you have them all in DataTables after the import from then on, they are all the same now so no matter where you got the data from, as long as you have a field to "Join on" you can create relations between them, is that Cool or Not ?, but I talk a bit ahead now, we still got to do that ;-)
Hence,let's get back to the Example :
If You did run the former code, the result should be that you have 2 Datasets that you can look at like this :
*tip* if you do not want to edit your default Types.MSHXML you can also use update typedata for this : see : Update-TypeData (Democracy to the types) there are also some examples of adding methods to Objects in that post.
but I liked this also as a short function,
Get-MSDN will get you to Root of the Library get-MSDN system.object will get you to The System.Object Page get-msdn system.object.GetHashCode will get you to The System.Object getHashCode Method Page.
so here an example to get the data from a table or view (query) in a SQL server to a DataSet in Monad.
we do not use the System.Data.OleDb here, for a SQL server connection there is a special namespace in .NET, system.data.sqlclient.
as indicated in the Excel post for the rest is basicly the same, only I did format the Connectionstring a bit by using a Stringbuilder to split it up in Parts.
you just fill the parameters,
$source = SQL server name $Catalog = the DateBase to Connect to : $view = the View or Table to load.
I HardCoded the "Select *", as you can filter later, but you can also change this.
Afer running this you can do things like this : (I had a Table with all users Loaded) (only I few as there are already more in the Excel example) the first export a selection of the table to CSV file.
The second example does get the colums, I added this example, because the whole Table is a Property of the Column (and gets Partly Expanded), so If you do not do this you can get a lot of output (I had 4000+ Records in my Dataset ;-)).
the last one, was a check how many users where in today (vacationtime ;-))
# Export All Users starting $ds.tables[0].select("adu_Name like 'Mow12*'") | export-csv
# Get all ColumnNames # (don't forget the Select on a Big Table, as the Whole Table is also a Property) $ds.tables[0].Columns | select ColumnName
# Users vandaag binnen :
MSH H:\> ($ds.tables[0].select("adu_LastLogon > '$((get-date).date)'") | ft | Measure-Object).count 115
For an example to Manualy Make a DataSet and an example of loading the dataSet into a DataGrid and show it on a Form you can look here : WMI viewer script for MSH (GUI)
You can see have SQL data in the MSH CLI is very handy for scripts that take a list, you don't need to export it first.
The following script will watch a 2003 Cluster for Events using a WMI Event Query.
As it’s the 100th post is a bit more descriptive ;-)
First it will make a WMI Connection to the Cluster, note that the Namespace path is not root\CimV2 but root\MSCluster as the Cluster WMI classes reside in there own namespace.
In the next part of the script we make the ManagementEventWatcher object. And we provide it with a Query.
Note we use MSCluster_Event , this is the BaseClass of all other MS_cluster event classes, so this will give us all Events, You can also use the more specific classes as MSCluster_EventGroupStateChange to get only that events.
Next I set a Timeout on the EventWatcher,
this is because you can not break the script with Ctrl-C when the EventWatcher is waiting for an event, (you can break with ctrl-Break but this will exit the complete MSH shell session.), I did set it to 1 second, so I can Break the script every 1 seconds, more about this you can find here :MSH directory watcher with popup-balloon(there I did attach it to a hidden form and a pop-up baloon so I could do A-Sync event handling, this time the Eventwatcher is not in another Thread state, but I could not get the eventhandlers working for the start() and Stop() Methods, I did get the Stopped eventhandler script-block fired but only after a WaitForNextEvent() call)
Note, that this is the Timeout on waiting for an event to come into the Client queue, not how often WMI is looking for Events !
That is stated in the Event Query :
SELECT * FROM MSCluster_Event WITHIN 10
The Within 10 is the interval that WMI will check for changes on the server side !. So WMI will check for changes every 10 seconds, we are only stating that we only wait 1 second for an event and then go on to check for a key press and look in the Queue again.
So with a 1 second wait, we can check more servers in this loop or we can set the timeout higher for example 1 minute, still WMI will check every 10 Seconds and put changes in the queue, (for the example as we check all the events so sometimes we get 10 events or more in a couple of seconds this is not very handy (it will take us 10 loops to read them out, but in a production environment a warning on only a group change would be enough.)
So be aware of the difference between WITHIN 10 in the Event Query and the timeout on the check of the Event watcher Queue!.
In the next part of the Script,
We call FillTables, this is a helper function below the main function in the script that fills some HashTable Variables to do the translation of the Eventcodes we get back from WMI. Note that it is called with a “Dot” so the variables get loaded in the current scope, in this case the Main function’s scope (That is the scripts not the Global!) as we do call it from there.
You see also that Main get called at the end of the script so that the FillTables function is declared before it gets called, but I still can place it below the main script.
Next the script enters the loop here :,
while (!$host.ui.rawui.KeyAvailable){
so it will loop until a key gets pressed (the timing we did in the event watcher timeout as explained earlier.
Then we wait for an event and display some info if we get an event.
First we have to translate the Date (as it is in Filetime Format).
"$([datetime]::fromfiletime($e.TIME_CREATED))"
Then we use the created HashTables to translate the codes we get back, for example :
“$($ClusterObject[[int]$e.EventObjectType])”
$e.EventObjectType is the Type of Object the change is about, this can be :.
This number we give as an index to the HashTable (I had to explicitly cast it to an INT to get this working) so we get a more descriptive value back for our Message.
We do this a couple of times more till we come to the EventNewState property.
This one is a bit tricky because the Code returned will have a different meaning Depending on the Object type of the Event.!!
So we do a Switch to select the right HashTable for the Translation. (note I did not implement all hashTables yet.)
After all the translations we have the Following output :
.\clusterWatch.msh ClusterName Watching ClusterName, Press Any Key to Quit
Cluster ClusterName Event : 12/28/2005 5:10:10 PM Group : TestGroup Change : GROUP_STATE Node : Node1 State : Online
With a Application Down the State of the Goup was PartialOnline, the resource comes online, hence the state of the resourcegroup will also change now.
Also you can see how important the Switch statement is, ! The Resourcestate 2 means "Online" and GroupstateType 2 means Fialed !!
That’s it,
my next step would be to get the messages in a popup-balloon as in the :MSH directory watcher with popup-balloon, example, I started with this but could not get the eventhandler scriptblocks called, but you still use the notify icon to output the Message, but I would do that Only for the GroupChanged Events By using the MSCluster_EventGroupStateChange Class, to not overflow yourself with balloons.
I will post a example to output the current status of a cluster later.
If you got an Excel sheet with Data, you can use the Excel.Application COM-object to get data from it, but you can also use ADO and a Dataset for this as I will show in this post.
For this example, I made a New Excel Sheet and entered the following Data and did save it as Puters.XLS.
Now we have the Excel sheet we will use Monad to get the Data.
Now we have got the Table(s) ready we can make a Select Query to get back the Data, I will use Select * and no filter as we will use the dataset for this later.
# make a Command to get all data of the first table (Sheet)
$cmd = new-object System.Data.OleDb.OleDbCommand("Select * from [$($tables.rows[0].TABLE_NAME])",$Conn)
Now we have the Command and the Connection ready we can make a DataAdapter that uses the Command to fill our DataSet:
So now the DataSet is filled, we are ready to use the data from Excel. As the data is in a DataSet now we can use DB methods like filters and Sorts on the Data as shown in the Examples Below:
As you can see having this data in a Dataset has advantages over the data in Excel as you directly query and filter the DataSet. Also you don't need the Excel COM object as you make a OBDC connection to the Excel Data.
Also there a lot more possibilities with the DataSet, for example add relations to other tables etc.
what make this a very powerfull way to get at your Excel Data. (Same goes for MsAccess for example) for SQL you will use the System.Data.SqlClient namespace, but the methods are almost the same, I will blog about that later.
Using MSH to Manage Your Music Files and Playlists,
I'm a big fan of the MS Scripting Guy's You Might have seen that they have opened a MSH portal on the ScriptCenter also. http://www.microsoft.com/technet/scriptcenter/hubs/msh.mspx Great !!, there are already a couple of sample scripts and an item about WMI in MSH
But also the other Articles a still of interest if you work with MSH, as the most are easy to translate, for Example :
It's about Scripting the Windows MediaPlayer, and did the same in MSH. (It's also nice to see that MSH works that much easier ;-))
I wil show you some examples below but as the article is exelent for the explanation I point you to the article, the examples will get you going doing it the Monad way.
First a Function to play a Playlist in Mediaplayer from MSH like this :
MSH> play-list maddness
the Playlist Function is just this.
function Play-List ([string]$name) { $WMP = new -com "WMPlayer.OCX" $wmp.openplayer($wmp.mediaCollection.getByName($name).item(0).sourceURL) }
And already Monad is Rocking ;-)
But there is More :
The Maddness playlist I created also from MSH, I added all MP3's from a directory, Created the Playlist , all from the Monad CLI. Also the are some searching examples below.
As said for the most this are just the examples from the original article, and for the most even simpler as in the origional examples, so for the explanation of the $WMP objectI point you there (also use Get-Member a lot as alway's in MSH ;-).
but there is an interesting thing to note on the Monad side in the scripts also.
Maybe you did allready note the foreach loops like this one
What happens here is that I use the Foreach as an For Loop.
0..($m.count - 1)
will just make a range of numbers. so I get all the Items till the last one (as we start with 0, we need 1 less as the count value) so this :
$m.item([int]$_).name
will get the Item, reason for this is that the Objects are not Array's and They don't have a GetEnumerator() Method, so we can't do a "normal" foreach.
you could also do this with a For loop, but if it's on one line I like this way more as the for loop contructor in MSH is a bit bigger, this would be the same with For :
Oops, not there either so this might be a good example for setting the Service account too ;-) b.t.w. I think I can come up with a .NET way, but it was just an example setting WMI properties anyway, so lets go on.
The question in the NG was about this line in Vbscript, acting on a win32_service instance, it would give back error 21 ( Invalid parameters have been passed to the service) see also GWM output below:
but that did not seem to help (strange as it did work when I tested it in a Vbscipt after the MSH check I will describe) but this got me started in MSH
First I took my Monad WMI Method Help script WMI help Part 3 (Methods) that also creates Sample Scripts to make a script : (use the link to get the updated version in a later post, that is the one used here) The command Get-WmiMethodHelp (GWM) will produce the following output : (b.t.w. it's a lot of output, but I will explain it below also it's not good pastable but I will post a paste-able script below also)
MSH>get-wmimethodHelp No WMIClass given MSH>gwm win32_service change win32_service : change :
The Change method modifies a service. The Win32_LoadOrderGroup parameter represents a grouping of system services defining execution depend encies. The services must be initiated in the order specified by the Load Order Group as the services are dependent on each other. These de pendent services require the presence of the antecedent services in order to function correctly.It returns one of the following integer val ues: 0 - The request was accepted. 1 - The request is not supported. 2 - The user did not have the necessary access. 3 - The service cannot be stopped because other services that are running are dependent on it. 4 - The requested control code is not valid, or it is unacceptable to the service. 5 - The requested control code cannot be sent to the service because the state of the service (Win32_BaseService:State) is equal to 0, 1, o r 2. 6 - The service has not been started. 7 - The service did not respond to the start request in a timely fashion. 8 - Unknown failure when starting the service. 9 - The directory path to the service executable was not found. 10 - The service is already running. 11 - The database to add a new service is locked. 12 - A dependency for which this service relies on has been removed from the system. 13 - The service failed to find the service needed from a dependent service. 14 - The service has been disabled from the system. 15 - The service does not have the correct authentication to run on the system. 16 - This service is being removed from the system. 17 - There is no execution thread for the service. 18 - There are circular dependencies when starting the service. 19 - There is a service running under the same name. 20 - There are invalid characters in the name of the service. 21 - Invalid parameters have been passed to the service. 22 - The account, which this service is to run under is either invalid or lacks the permissions to run the service. 23 - The service exists in the database of services available from the system. 24 - The service is currently paused in the system. Other - For integer values other than those listed above, refer to Win32 error code documentation.
change Parameters :
Name = DesktopInteract Type = boolean Optional = False
Name = DisplayName Type = string Optional = False
Name = ErrorControl Type = uint8 Optional = False
Name = LoadOrderGroup Type = string Optional = False
Name = LoadOrderGroupDependencies Type = string Optional = False
Name = PathName Type = string Optional = False
Name = ServiceDependencies Type = string Optional = False
Name = ServiceType Type = uint8 Optional = False
Name = StartMode Type = string Optional = False
Name = StartName Type = string Optional = False
Name = StartPassword Type = string Optional = False
Sample Script :
# win32_service change-Method Sample Script # Created by Get-WmiMethodHelp # /\/\o\/\/ 2005 # Fill InParams values before Executing # InParams that are Remarked (#) are Optional
First You see that after a short description, all the return values are listed, very handy later on ;-)
after that all the Parameters of the Change Method, note that they are all mandatory. (I will explain why later on)
and the generated script, I pasted that in notepad, and started to fill in.
First as this Method works agains an instance I needed to provide the Key properties of the service I want to change, in this case the name (GWM did lookup that) I provided Alerter for this test:
than I need to fill the Properties, as get-WmiMethodHelp also did look them up for me, also you can see that it did look up the type of variable WMI expects for that property, you can fill in the value after them.
Now you see something interesting, the script does say that all Properties are Mandatory so We have to provide them, but as we see the Vbscript version (also the working) leaves them empty
Set SWBemlocator = CreateObject("WbemScripting.SWbemLocator") Set objWMIService = SWBemlocator.ConnectServer(strComputer,"\root\CIMV2") Set colItems = objWMIService.ExecQuery("Select * from Win32_Service where Name = 'Alerter'",,48)
For each objService in colItems errServiceChange = objService.Change( , , , , , ,".\mow","test") next
wscript.echo(errServiceChange )
as explained a bit already in the GWM post, there is something weird about the WMI Optional Qualifier, as explained in that post a Qualifier is mandatory if the Optional Qualifier is NOT found, and now we see that the are not Mandatory ?
At First it may look that way, but you might have noticed that the Vbscript version did contain a lot of comma's before the startname and Password value. these are not realy empty Vbscript fills them with Null values !
so what is the case here is that the Values ARE mandatory, but that they CAN be $null, that mean's do not change them. that what is happening in the Vbscript version.
We get an 21 Error also, but if you look in the output you might already See what is going wrong, as the script contains [string] as an helper already it fill's the parameter with an empty string.
but if we cange all other parameters to $null, like this : $InParams["DesktopInteract"] = $null etc.
the sample script will work. but the good news is the $InParams standard fills the properties with $null, so we can just leave out those lines.
So you see the mandatory Properties in WMI are not alway's so mandatory as they seem sometimes (as in this case) this could be a bit confusing (have to set to $null or delete the lines in the generated script), but there is no way for the get-WmiMethodHelp script to see this, even with extendedQualifiers (for more info, see WMI help serie), sometimes this $null behavour is mentioned in the Embedded help in the WMI class, shown by Get-WmiMethodHelp but as you see in this example not always.
but as you can see now, we are still a lot better of as in the Vbscript example as we also have to look up the order and the number of properties in the SDK there :
so as you can see this behavour of WMI is a bit confusing but I think that the $null is handled in the Provider so WMI also does not know, only I should think this should be in the MOF help, (you can b.t.w. always add it to the MOF is you want) but as also explained before the help in the MOF's is not alway's as good as we would want.
hope I could explain this behavour a bit in this (very Long) post, and hope you don't see this as a bug in get-WmiMethodHelp as it say's optional properties should be Commented out ;-)
You can open this file in IE to see how it looks, but as you can see below and in the entry "Read the raw Monad XML helpfiles " Monad has also some nice way's to walk the XML
MSH>(get-WMIObject win32_operatingsystem).GetText("foo") Cannot convert argument "0", with value: "lol", for"GetText" to type "System.Management.TextFormat". At line:1char:46
you might think that you can do that from the WMI object also and more easy maybe, but think about it that we are working with a file, you can send it to someone and they can do the same querying with it, or apply formatting.
Also you can process this XML to create a webpage or something, using styles and Xpath.
I thinking about a entry about searching XML from MSH using Xpath etc, and firing up a treeview from MSH but my XML needs a freshup first.
but you can see there are a lot more of uses of having this info in XML format.
IF you have a directory shared and you delete the Directory from a Command prompt or script, the share will stay behind in the Registry, this is called an Orphaned Share.
(if you do this from the Explorer you get a message that it is shared, and the share will be removed, but not if this is done from the commandline)
so if you remove a directory that is shared, the value in the registry will remain and the server service will try to recreate them whan the computer is restarted.
if you restart the computer you get errormessages like this in the Eventviewer :
Description: The server service was unable to recreate the share wees$ because the directory c:\wees no longer exists.
This Script will Check and clean those "Orphaned Shares" on the local or on a remote computer.
To be a bit more safe, I set the ErrorActionPreference to Stop and the script will only check and not change anything if the DoChange parameter is not set to $true.
Ofcourse it's still a good Idea to backup the registry key first. As I will not give the standard disclaimer, but it's the registry we are changing.
and as all the scripts on this blog, it's an example not a production script, actualy I DID run this agains a production server, to remove a lot of event-entries at boot ;-), but I backuped and double checked everything before running and did - ofcourse, as I made it - know exactly what the script did, but i would not give this to someone to do regular cleaning, that would require a "production-script" that is completely tested, so be sure to know the script before running with $true parameters ;-).
OK, enough warnings, let's go on,
the script works like this : (Note, I run it 3 times, first to check, then to change (the counter will not change, as it counted the old data, then to check again.)
# Check Mode
.\OrphanShares.msh ComputerName
public$ Exists wees$ is an Orphan ...
Orphan : 323 Existing : 1931
# Change Mode (DoChange Parameter given,Orphan shares will be removed)
.\OrphanShares.msh ComputerName $true
public$ Exists wees$ is an Orphan wees$ Deleted !!
Orphan : 323 Existing : 1931
# Third Run (Check Mode again)
.\OrphanShares.msh ComputerName
public$ Exists ...
Orphan : Existing : 1931
first, I checked the server (remote) for "Orphaned shares", I did cut out most of the output but there where 323 orphaned shares (I knew as the server had rebooted this morning giving me all the EventId 2511 Errors in the eventlog, what started this all ;-) )
then I did do the same with DoChange set to $true. (note that the count is not updated yet)
and then I check again, ... Yes they are gone .. As you can see we have 323 Orphan shares less now (And "more important" the "annoying" 323 matching event-entries ;-)) and then the script :
Line 15 Connects to the remote registry Line 16 gets the LanmanServer Shares key
line 20 Gets all the ShareNames into the $RegShares array
line 24 Does the same but then with WMI that will give only the shares that realy exist on the system, these Sharenames go into the $shares Array.
in the loop starting on Line 28, 2 things are interesting :
Line 29 : if ($shares -eq $_) as $_is the Current ShareName from $regShares the -eq will check against the WHOLE array, so we don't need an extra loop here.
Line 34 : we will only delete the (current)share from the registry as the -Dochange parameter is set to $true.
line 40 + 41 give the statistics the statistic checks do an extra loop, as I did not bother about performance to much, but this could be better added to the main loop.
this started as a 5 line commandline history that I copied in notepad while doing this at work (good excuse to use Monad.. ehh.. I mean WMI and the registry).
I did the action just by doing the actions on the commandline.. Use the history buffer till the command does exactly what I want... paste the command in notepad... so at the end of the action you have the script as result, and while working you can just copy parts of the textfile into the Commandline interface, to run parts of the script (to be), for example to reset things.
I just love this way of working.. that's what makes Monad so powerfull and quick.