/\/\o\/\/ PowerShelled

This blog has moved to http://ThePowerShellGuy.com Greetings /\/\o\/\/
$AtomFeed = ("Atom.xml")
$PreviousItems = (" Moving to ThePowerShellGuy.com "," PowerShell Code formatting test for my new Blog "," PowerShell Code formatting test for my new Blog "," PowerShell Community Extensions V1.0 "," PowerShell : Access remote eventlogs "," Windows PowerShell Scripting Sweepstakes! "," PowerShell : making Custum Enums "," TechNet Webcast: An Overview of Windows PowerShell "," "Windows PowerShell: TFM" goes "Gold" "," PowerShell : Advanced renaming of files "," ")

Friday, December 30, 2005


MSH GUI Viewer for Dataset Example

A viewer for the dataset created in former Post, More DataTables and joining them in Monad to Show the Relations in a Datagrid,

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

$form = new-object "System.Windows.Forms.Form"
$form.Size = new-object System.Drawing.Size @(800,400)
$DG = new-object "System.windows.forms.DataGrid"
$DG.CaptionText = "Example for http://mow001.blogspot.com/2005/12/more-datatables-and-joining-them-in.html"
$DG.AllowSorting = $True
$DG.DataSource = $Ds.mshobject.baseobject
$DG.Dock = [System.Windows.Forms.DockStyle]::Fill

#show the Form

$form.text = "DataSet Viewer Example"
$form.topmost = $true

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)

Tags :


More DataTables and joining them in Monad

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"


$row = $dt.NewRow();$row.cmp_Name = "Computer1";$row.cmp_User = "Mow";$dt.rows.add($row)
$row = $dt.NewRow();$row.cmp_Name = "Computer2";$row.cmp_User = "Monad";$dt.rows.add($row)
$row = $dt.NewRow();$row.cmp_Name = "Computer3";$row.cmp_User = "Monad";$dt.rows.add($row)
$row = $dt.NewRow();$row.cmp_Name = "Computer4";$row.cmp_User = "Mow";$dt.rows.add($row)
$row = $dt.NewRow();$row.cmp_Name = "Computer5";$row.cmp_User = "Foo";$dt.rows.add($row)
$row = $dt.NewRow();$row.cmp_Name = "Computer6";$row.cmp_User = "Bar";$dt.rows.add($row)

$dtComputer = $dt

$dt = new system.data.dataTable
$dt.TableName = "tblUser"


$row = $dt.NewRow();$row.usr_Name = "Mow";$row.usr_Dep = "Dep1";$dt.rows.add($row)
$row = $dt.NewRow();$row.usr_Name = "Monad";$row.usr_Dep = "Dep1";$dt.rows.add($row)
$row = $dt.NewRow();$row.usr_Name = "Foo";$row.usr_Dep = "Dep2";$dt.rows.add($row)
$row = $dt.NewRow();$row.usr_Name = "Bar";$row.usr_Dep = "Dep2";$dt.rows.add($row)

$dtUser = $dt

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 :

cmp_Name cmp_User
-------- --------
Computer1 Mow
Computer2 Monad
Computer3 Monad
Computer4 Mow
Computer5 Foo
Computer6 Bar


usr_Name usr_Dep
-------- -------
Mow Dep1
Monad Dep1
Foo Dep2
Bar Dep2

Normaly you should have some more fields but for the example this will do.

Now you can answer questions from Monad like,
What are Mow's Computers, or how Many Users Dep2 got :
MSH>$dtcomputer.select("cmp_User = 'Mow'")

cmp_Name cmp_User
-------- --------
Computer1 Mow
Computer4 Mow

MSH>$dtUser.select("usr_Dep = 'Dep2'").count

But now You boss does ask How Many Computers does Dep1 use ?
Oops, there is no Department foeld in the Computer Table.

In this case you can figure this out, by counting by head but imagine that there are 1000 computers and 800 Users how to go on ?.

You need to combine the info of the 2 tables.
Here the Relations come to the resque.

In this Case we see That the UserName is know in both Tables,
In tblUser as usr_Name and in tblComputer as cmp_User.

so if we "join" those to field we can get from a User to his Computers.

To make a relation between to DateTables they have to be in the same dataset so we add them to a dataset and make the relation :

# Create A DataSet and add the 2 Tables

$ds = new-object system.data.dataset

# Create a Relation Based on the Username

$Parent = $ds.tables.item("tblUser").columns.item("usr_Name")
$Child = $ds.tables.item("tblComputer").columns.item("cmp_User")
$rel = new-object System.Data.DataRelation("UserToComputer",$Parent,$child,$false)

# Add the realation to the DataSet

*Note* the $false means "No Constrains", as we use this only for a query.

Now we have a Relation added to the DataSet, we can use it to Combine the data from
both Tables.

Hence, we are now able to answer the Question our boss asked.

MSH>$ds.tables.item("tbluser").select("usr_dep = 'Dep1'") | foreach {$_.GetChildRows("UserToComputer")}

cmp_Name cmp_User
-------- --------
Computer1 Mow
Computer4 Mow
Computer2 Monad
Computer3 Monad

MSH>($ds.tables.item("tbluser").select("usr_dep = 'Dep1'") | foreach {$_.GetChildRows("UserToComputer")}).count

Very Cool, you boss does say .... Get this software deployed to them ;-)

and start thinking getWmiObject -computer $_.cmp_name win32_product ...
......invokeMethod ... Install ...

OK you say, you want it as a oneliner , or can I create the script ?

Have fun

greetings /\/\o\/\/

Tags :

posted by /\/\o\/\/

MSH Get-MSDN Function

I made this small function to start the MSDN library in the standard browser for a Typename.

Function Get-MSDN ($type = "default") {
  (new-object -com shell.application).Open("http://msdn2.microsoft.com/library/$type.aspx")

Inspired by a BlogItem on the Monad Team Blog, that adds a helplink to the get-member data.

Getting MSDN help urls for .NET BCL types and Members

*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,

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.

gr /\/\o\/\/

posted by /\/\o\/\/

Thursday, December 29, 2005


Getting and Working With SQL Server data in Monad

In my post about getting data from Excel in MSH : Using ADO to get data from Excel to Dataset in Monad. , I promised an SQL server example.

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.

So now it looks like this.

$Source = "SQLServer"
$Catalog = "MowMON"
$view = "tblAdUsers"

$SB = New-object System.Text.StringBuilder
$SB = $SB.Append("Integrated Security=SSPI")
$SB = $SB.Append(";Persist Security Info=False")
$SB = $SB.Append(";Initial Catalog=$Catalog")
$SB = $SB.Append(";Data Source=$Source")

$SQLcon = New-object system.data.sqlclient.SqlConnection
$SQLcon.ConnectionString = $SB.ToString()

$SelectCMD = New-object system.data.sqlclient.SqlCommand
$SelectCMD.CommandTimeout = 30
$SelectCMD.Connection = $SQLCon

$SelectCMD.CommandText = "Select * from $view"

$da = new-object System.Data.SqlClient.SQLDataAdapter($SelectCMD)
$ds = new-object System.Data.dataset

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

For some more Examples on using the DataSet you can look in the Exel Example :Using ADO to get data from Excel to Dataset in Monad.

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.

greetings /\/\o\/\/

Tags :

posted by /\/\o\/\/

Wednesday, December 28, 2005


MSH Cluster Watcher script

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 :


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).


Then we use the created HashTables to translate the codes we get back, for example :


$e.EventObjectType is the Type of Object the change is about, this can be :.

0 = "Handle";
1 = "Cluster";
2 = "Node";
3 = "Group";
4 = "Resource";
5 = "ResourceType";
6 = "Network";
7 = "NetworkInterface";
8 = "Registry";
9 = "Quorum"

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

12/28/2005 5:10:10 PM
Resource : TestApp
Node : Node1
State : OnlinePending

In this case I Brought a Test Application on Line.
But As we did subscribe to all events we get some more events as the loops goes on.

Cluster ClusterName
Event : 12/28/2005 5:10:10 PM
Group : TestGroup
Node : Node1
State : Pending

Cluster ClusterName
Event : 12/28/2005 5:10:10 PM
Resource : TestApp
Node : Node1
State : Online

Cluster ClusterName
Event : 12/28/2005 5:10:10 PM
Group : TestGroup
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.

gr /\/\o\/\/

Reference :

And The Script !

# ClusterWatch.msh
# Watch a Cluster for Events,
# /\/\o\/\/ 2005
# http://mow001.blogspot.com

Param ($cluster = "ClusterName")

Function Main {
  "Watching $cluster, Press Any Key to Quit"
  trap {Break}

  # Make a WMI Connection to the Cluster

  $ms = new-object system.management.managementscope
  $ms.path = "\\$cluster\root\MSCluster"

  # Make Event Watcher

  $ew = new-object system.management.ManagementEventWatcher
  $ew.scope = $ms
  $ew.query = "SELECT * FROM MSCluster_Event WITHIN 10"
  $opt = new-object System.Management.EventWatcherOptions
  $opt.Timeout = [timespan]::FromSeconds(1)
  $ew.options = $opt

  # Fill Lookup Tables :

  . FillTables

  # Wait for event :

  while (!$host.ui.rawui.KeyAvailable){
    trap {Continue}
    $e = $null
    $e = $ew.WaitForNextEvent() 2> $null
    if ($e) {
      "`nCluster $cluster Event :`n"
      "$($ClusterObject[[int]$e.EventObjectType]) : $($e.EventObjectName)"
      "Change : $($ChangeType[[int]$e.EventTypeMinor])"
      "Node : $($e.EventNode)"
      switch  ($e.EventObjectType) {
        2 {"State : $($NodeState[[int]$e.EventNewState])"}
        3 {"State : $($GroupstateType[[int]$e.EventNewState])"}
        4 {"State : $($ResourceState[[int]$e.EventNewState])"}

# Definition of LookUp Tables :

Function FillTables {

  $NodeState = @{
    0 = "Up"
    1 = "Down"
    2 = "Joining"  
    3 = "Paused"
    -1 = "StateUnknown"

  $GroupstateType = @{
    -1 = "StateUnknown";
    0 = "Online";
    1 = "Offline";
    2 = "Failed";
    3 = "PartialOnline";
    4 = "Pending"

  $ResourceState = @{
    -1 = "StateUnknown"
    0 = "Inherited"
    1 = "Initializing"
    2 = "Online"
    3 = "Offline"
    4 = "Failed"
    128 = "Pending"
    129 = "OnlinePending"
    130 = "OfflinePending"

  $ClusterObject = @{
    0 = "Handle";
    1 = "Cluster";
    2 = "Node";
    3 = "Group";
    4 = "Resource";
    5 = "ResourceType";
    6 = "Network";
    7 = "NetworkInterface";
    8 = "Registry";
    9 = "Quorum"

  $ChangeType = @{
    0x00000001  =  "NODE_STATE";
    0x00000002  =  "NODE_DELETED";
    0x00000004  =  "NODE_ADDED";
    0x00000008  =  "NODE_PROPERTY";
    0x00000010  =  "REGISTRY_NAME";
    0x00000020  =  "REGISTRY_ATTRIBUTES";
    0x00000040  =  "REGISTRY_VALUE";
    0x00000080  =  "REGISTRY_SUBTREE";
    0x00000100  =  "RESOURCE_STATE";
    0x00000200  =  "RESOURCE_DELETED";
    0x00000400  =  "RESOURCE_ADDED";
    0x00000800  =  "RESOURCE_PROPERTY";
    0x00001000  =  "GROUP_STATE";
    0x00002000  =  "GROUP_DELETED";
    0x00004000  =  "GROUP_ADDED";
    0x00008000  =  "GROUP_PROPERTY";
    0x00010000  =  "RESOURCE_TYPE_DELETED";
    0x00020000  =  "RESOURCE_TYPE_ADDED";
    0x00100000  =  "NETWORK_STATE";
    0x00200000  =  "NETWORK_DELETED";
    0x00400000  =  "NETWORK_ADDED";
    0x00800000  =  "NETWORK_PROPERTY";
    0x01000000  =  "NETINTERFACE_STATE";
    0x02000000  =  "NETINTERFACE_DELETED";
    0x04000000  =  "NETINTERFACE_ADDED";
    0x08000000  =  "NETINTERFACE_PROPERTY";
    0x10000000  =  "QUORUM_STATE";
    0x20000000  =  "CLUSTER_STATE";
    0x40000000  =  "CLUSTER_PROPERTY";
    0x80000000  =  "HANDLE_CLOSE"

. main 

posted by /\/\o\/\/

Monday, December 26, 2005


Monad NewYears Clock

Getting a clock Counting down the time till new Year,
and giving the current day and time.

Enjoy and Best wishes for 2006


# Clock.msh
# gives time till NewYear and clock
# /\/\o\/\/ 2005

function Main {
  $pos  = $host.ui.rawui.windowposition
  $size = $host.ui.rawui.buffersize
  $rect = "system.management.automation.host.rectangle" 
  $posOld = $pos 
  $re = new-object $rect $pos.x,$pos.y,$size.width,($pos.y + 10
  $buffer = $host.ui.rawui.getbuffercontents($re) 
  While (!$host.ui.rawui.KeyAvailable) {
    $pos = $posOld
    ([datetime]"1-1-2006").Subtract((get-date)).tostring().Substring(0,([datetime]"1-1-2006").Subtract((get-date)).tostring().LastIndexOf(".")).getEnumerator() | foreach {
      DrawNum $_  "Red"
      $pos.x += 4
    $pos.Y += 6 ; $pos.x = 0
    ((get-date)).tostring("dd-MM HH:mm").getEnumerator() | foreach {
      DrawNum $_  "Green"
      $pos.x += 4
    sleep 1

Function DrawNum ($num = 0 ,$color){
    0 {$ia = (367,61)} 
    1 {$ia = (292,36)} 
    2 {$ia = (487,57)} 
    3 {$ia = (487,60)} 
    4 {$ia = (493,36)} 
    5 {$ia = (463,60)} 
    6 {$ia = (463,61)} 
    7 {$ia = (295,36)} 
    8 {$ia = (495,61)} 
    9 {$ia = (495,60)} 
    ":" {$ia = (16,2)} 
    "." {$ia = (0,2)} 
    " " {$ia = (0,16)} 
    "-" {$ia = (448,0)} 

  $row = ""
  foreach ($int in $ia) {
    0..8 | foreach {
      if ($int -band [math]::pow(2,$_)) {$row += [char]9608else {$row +=" "}
      if (($_ +1)%3 -eq 0) {$row += "`n"}
  $Buf = $host.ui.rawui.NewBufferCellArray(($row.trimEnd().Split("`n")),[System.ConsoleColor]$color,"black")

. main

posted by /\/\o\/\/

Friday, December 23, 2005


Using ADO to get data from Excel to Dataset in Monad.

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.

First let's make a connection to the Excel Sheet:

$excel = "C:\Puters.xls"

# Make the Connection :

$ConnString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=$excel;Extended Properties=Excel 8.0;"
$Conn = new-object System.Data.OleDb.OleDbConnection($connString)

# Open the Connection


then we will get the Available Tables (there will be three as every sheet will be a Table, and Excel starts standard with 3 sheets)

# Get the available Tables

$Tables = $Conn.GetOleDbSchemaTable([System.Data.OleDb.OleDbSchemaGuid]::tables,$null)

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:

# Load a DataAdapter with the Select Command.

$da = new-object System.Data.OleDb.OleDbDataAdapter($cmd)

# Fill a DataSet from the Data Adapter

$ds = new-object System.Data.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:

# get the Tables


# get all Row's where User is Mow1

$ds.tables[0].select("user = 'Mow1'")

# Some More Selects and Sorts

# As is :

MSH C:\> $ds.tables[0]

Computer                                                    User
--------                                                    ----
Pc1                                                         Mow1
Pc2                                                         Mow2
Pc5                                                         Mow
Pc4                                                         Mow

# Only PC's of user Mow

MSH C:\> $ds.tables[0].select("user = 'Mow'")

Computer                                                    User
--------                                                    ----
Pc5                                                         Mow
Pc4                                                         Mow

# Only users ending with a 2 

MSH C:\> $ds.tables[0].select("user like '*2'")

Computer                                                    User
--------                                                    ----
Pc2                                                         Mow2

# All records Sorted Ascending

MSH C:\> $ds.tables[0].select($null,"Computer ASC")

Computer                                                    User
--------                                                    ----
Pc1                                                         Mow1
Pc2                                                         Mow2
Pc4                                                         Mow
Pc5                                                         Mow

# All Records Descending

MSH C:\> $ds.tables[0].select($null,"Computer DESC")

Computer                                                    User
--------                                                    ----
Pc5                                                         Mow
Pc4                                                         Mow
Pc2                                                         Mow2
Pc1                                                         Mow1

To Finish this entry, I will show you how you can save to and Reload the Generated DataSet to and from an XML-file.
# Save this Dataset as XML Document:

$sw = new-object system.io.streamwriter("C:\Puters.xml")

# Load XML document into new dataset :  

$sr =  new-object system.io.streamreader("C:\Puters.xml")

MSH C:\> $ds2.tables

Computer                                                    User
--------                                                    ----
Pc1                                                         Mow1
Pc2                                                         Mow2
Pc5                                                         Mow
Pc4                                                         Mow

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.

gr /\/\o\/\/

posted by /\/\o\/\/

Thursday, December 22, 2005


Monad Really Does Rock

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.
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 :

I stole the Title and Subtitle from the following Scripting Guys Article.

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"

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.

More Samples :

# Make a MediaPlayer Object

$WMP = new -com "WMPlayer.OCX"

# get Media Collection

$mc = $WMP.mediaCollection

# get All Items in the Media Collection 

$m = $mc.getall()

0..($m.count - 1) | foreach {"$_ $($m.item([int]$_).name)"}

# get only the Audio Items :

$List = MC.getByAttribute("MediaType""Audio")
0..($list.count - 1) | foreach {"$_ $($list.item([int]$_).name)"}

# List With Duration

0..($list.count - 1) | foreach {$list.item([int]$_)} | ft name,durationstring

# Get Other Atributes


# list all available atributes :

0..($list.item(25).attributeCount - 1) | foreach {$list.item(25).getAttributeName($_)}

# play the File in Mediaplayer


# Add some Madness Songs

(ls g:\mp3\Madness) | foreach {$mc.add($_.fullname)}

# Make new playlist

$pl = $WMP.playlistcollection.newplaylist("Madness")

# Get all Madness Songs

$Songs = $mc.getByAuthor("Madness")

# add them to the new playlist

0..($songs.count - 1) | foreach {$pl.appendItem($songs.item([int]$_))}

# Open the created songlist


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
0..($m.count - 1) | foreach {"$_ $($m.item([int]$_).name)"}

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 :


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 :
For ($i = 0 ; $i -lt $m.count ; $i++) {"$_ $($m.item([int]$i).name)"}

In the Scripting Guys article are some more examples, but it's more of the same so this will get you going translating the rest to MSH.

as as said it's a very good read.

Enjoy "Rocking Monad"

greetings /\/\o\/\/

posted by /\/\o\/\/

Wednesday, December 21, 2005


Passing (not so) mandatory Parameters to WMI methods in MSH

I do this blogitem about a question in the NG, about setting the name and password of a service, I will show a MSH WMI script for that.

I wanted to start this entry with showing a simpler way from Monad but failed to do it.

first I tryed :
set-service [-ServiceName] System.String [-DisplayName] System.String [[-Description] System.String] [[-StartupType] {...}]

Hmm, no parameter for that.

no, problem I did think just take a method of the service :
MSH>(get-service "Alerter") | gm

TypeName: System.ServiceProcess.ServiceController

Name MemberType Definition
---- ---------- ----------
set_DisplayName Method System.Void set_DisplayName(String value)
set_MachineName Method System.Void set_MachineName(String value)
set_ServiceName Method System.Void set_ServiceName(String value)
set_Site Method System.Void set_Site(ISite value)

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:
 errServiceChange = objService.Change( , , , , , ,"Domain\User")
errPasswdChange = objService.Change( , , , , , , ,"Passwd")
wscript.echo(errServiceChange &":"& errPasswdChange)

I first recommended hem to set them on one line

errServiceChange = objService.Change( , , , , , ,"Domain\User","Passwd")

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)
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
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

$Class = "win32_service"
$Method = "change"
$Computer = "."

#win32_service Key Properties :
$Name = [string]

$filter = "Name = '$Name'"
$MC = get-WMIObject $class -computer $Computer -filter $filter

$InParams = $mc.GetMethodParameters($Method)

$InParams["DesktopInteract"] = [boolean]
$InParams["DisplayName"] = [string]
$InParams["ErrorControl"] = [uint8]
$InParams["LoadOrderGroup"] = [string]
$InParams["LoadOrderGroupDependencies"] = [string]
$InParams["PathName"] = [string]
$InParams["ServiceDependencies"] = [string]
$InParams["ServiceType"] = [uint8]
$InParams["StartMode"] = [string]
$InParams["StartName"] = [string]
$InParams["StartPassword"] = [string]

"Calling win32_service : change with Parameters :"
$inparams.get_properties() | select name,Value

$R = $mc.InvokeMethod($Method, $inParams, $Null)
"Result : "


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:
#win32_service Key Properties :
$Name = [string]"Alerter"

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

the working VB example, I wrote later :
strComputer = "."
Set objWMIService = GetObject("winmgmts:" _
& "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")

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")

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.

So if We change Only This lines,
$InParams["StartName"] = [string]".\mow"
$InParams["StartPassword"] = [string]"test"

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

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 the final script will look like this :

$Class = "win32_service"
$Method = "change"
$Computer = "."

#win32_service Key Properties :
$Name = [string]"Alerter"

$filter = "Name = '$Name'"
$MC = get-WMIObject $class -computer $Computer -filter $filter

$InParams = $mc.GetMethodParameters($Method)

$InParams["StartName"] = [string]".\mow"
$InParams["StartPassword"] = [string]"test"

"Calling win32_service : change with Parameters :"
$inparams.get_properties() | select name,Value

$R = $mc.InvokeMethod($Method, $inParams, $Null)
"Result : "

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 ;-)

gr /\/\o\/\/

posted by /\/\o\/\/

Tuesday, December 20, 2005


Saving a WMI instance to a XML-file from MSH

if you want to save all the information of the win32_operatingsystem WMI class to an XML file,it's as easy as this in Monad :
([xml](get-WMIObject win32_operatingsystem).GetText("WmiDtd20")).save("c:\os.xml")

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

Some more info and examples :
# get methodinfo

MSH>(get-WMIObject win32_operatingsystem).GetText

MemberType : Method
OverloadDefinitions : {System.String GetText(TextFormat format)}
TypeOfValue : System.Management.Automation.MshMethod
Value : System.String GetText(TextFormat format)
Name : GetText
IsInstance : True

# get the full Classname

MSH>(get-WMIObject win32_operatingsystem).GetText("foo")
Cannot convert argument "0", with value: "lol", for "GetText" to type "System.Management.TextFormat".
At line:1 char:46

# get the formats


# get te MOF text

(get-WMIObject win32_operatingsystem).GetText("Mof")

# get the XML text.

MSH>(get-WMIObject win32_operatingsystem).GetText("WmiDtd20")
<INSTANCE CLASSNAME="Win32_OperatingSystem"><QUALIFIER NAME="dynamic" PROPAGATED="true" TYPE="boolean" TOSUBCLASS="false" TOINSTANCE="true"

# get the instance to an XML document

[xml]$os = (get-WMIObject win32_operatingsystem).GetText("WmiDtd20")




--------- --------- -------- --------------
Win32_OperatingSystem {dynamic, Locale, provider, UUID} {__PATH, __NAMESPACE, __SERVER,... PROPERTY.ARRAY


---- ----------- ---- -----
__PATH ___SYSTEM string \\puter\root\cimv2:Win32_O...
__NAMESPACE ___SYSTEM string root\cimv2

# a single property


NAME : BuildNumber
CLASSORIGIN : Win32_OperatingSystem
TYPE : string
VALUE : 2600

# back to XML

<PROPERTY NAME="BuildNumber" CLASSORIGIN="Win32_OperatingSystem" TYPE="string"><QUALIFIER NAME="CIMTYPE" PROPAGATED="true" TYPE="string" TO

# count properties


# list propertynames

MSH>$os.INSTANCE.property | foreach {$_.name}

# Save The XML to a XML file


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.

gr /\/\o\/\/

posted by /\/\o\/\/

Monday, December 19, 2005


MSH Orphan share remover Tool

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 :

Event Type:        Error
Event Source: Server
Event Category: None
Event ID: 2511
Date: 19-12-2005
Time: 7:22:22
User: N/A
Computer: ComputerName

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 :
Existing :

# Change Mode (DoChange Parameter given,Orphan shares will be removed)

.\OrphanShares.msh ComputerName $true

public$ Exists
wees$ is an Orphan
wees$ Deleted !!

Orphan :
Existing :

# Third Run (Check Mode again)

.\OrphanShares.msh ComputerName

public$ Exists

Orphan :
Existing :

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 :
  1 # OrphanShares.msh
  2 # Checks / Removes Orphan Shares
  3 # /\/\o\/\/ 2005
  5 param (
  6   [string] $Computer = ".",  
  7   [bool] $DoChange = $false
  8 )
 10 # to be safe stop at errors.
 11 $ErrorActionPreference = "stop"
 13 # Connect to registry
 15 $Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $Computer)
 16 $key = $Reg.OpenSubKey("SYSTEM\CurrentControlSet\Services\LanmanServer\Shares", $True)
 18 # get shares in Registry :
 20 $Regshares = $Key.GetValueNames()
 22 # get Existing shares :
 24 $shares = @();get-WMIObject -computer $Computer win32_share | foreach {$shares += $_.name}
 26 # Compare the Lists :
 28 $RegShares | foreach {
 29   if ($shares -eq $_) {
 30     "$_ Exists"
 31   }Else {
 32     "$_ is an Orphan"
 33     # Only if DoChange is True Delete The Orphan Share
 34     if ($Dochange) {$Key.DeleteValue($_);"$_ Deleted !!"}
 35   }
 36 }
 38 # show some statistics :
 40 "Orphan   : ";($RegShares | foreach {"$_ $($shares -eq $_)"} | findstr "False" | measure-object).count
 41 "Existing : ";($RegShares | foreach {"$_ $($shares -eq $_)"} | findstr "True" | measure-object).count 

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.

gr /\/\o\/\/

posted by /\/\o\/\/


October 2005   November 2005   December 2005   January 2006   February 2006   March 2006   April 2006   May 2006   June 2006   July 2006   August 2006   September 2006   October 2006   November 2006   December 2006  

$Links = ("PowerShell RC1 Docs"," PowerShell RC1 X86"," PowerShell RC1 X64"," Monad GettingStarted guide"," Monad Progamming Guide"," Monad SDK"," Monad videos on Channel 9"," MSH Community Workspace"," scripts.readify.net "," MonadSource"," www.reskit.net"," PowerShell Blog"," Under The Stairs"," computerperformance powershell Home"," proudlyserving"," MSH on wikipedia"," MSHWiki Channel 9"," Keith Hill's Blog"," Precision Computing"," PowerShell for fun"," MSH Memo (Japanese)"," monadblog")

find-blog -about "PowerShell","Monad" | out-Technorati.
find-blog -contains "","" | out-Technorati.
Web mow001.blogspot.com

This page is powered by Blogger. Isn't yours?