Sunday, January 19, 2014

[PowerShell + WMI Eventing ] AD Group Modified --> Run a PowerShell Script

I knew it would be a great learning opportunity when Francois-Xavier Cat Sir agreed to take me in his team for the Winter Scripting Games this year.

FX Sir posted about this great Script which will monitor your AD Group for any changes and then email you the changes if any.
You can find the blog post here

Now while reading the post I had a question...So I did comment on the post there:

Well until recently I found out that it could be done very easily.
So in today's blog post I will show how to trigger a PowerShell Script when a Group in AD is modified.

I got the answer to above question while reading this great book on WMI by Ravi Sir suggested to me by Laerte Junior SirYou can find the book here

If you haven't guessed it by now the answer to making it possible is --- Wait for it "WMI Eventing".

I read this very cool thing on a blog:
                 "The M in WMI stands for Magic"  - Kim Oppalfens


So without further delay let's dive into the stuff.
I have two Scripts in place..doing all this on my Domain Controller:
  •  Dex.ps1 -- Which will create the permanent Event Consumer to monitor the Group.
  • Test.ps1 -- The Script which gets executed when the Event occurs.

To be more precise here the "Event" here is when the changes are made to the AD group (in my example "DexTestGroup") and this event will fire up our script Test.ps1 (for simplicity hardcoded this one).

So below is the Dex.ps1 which will take the Group name as an argument which needs to be monitored of the changes :


Now I have a simple test.ps1 which doesn't do much ...have a look below




The Dex.ps1 creates a Event Filter and then creates a CommandLine Event Consumer and then finally binds those two together......Yeah and it uses CIM Cmdlets which makes life pretty easy.

Yay!! demo time:

I have a test group in my AD by the name "DexTestGroup" and it doesn't have any members yet ....see below:



Now let me setup the monitor for this Group before I do any changes:

All set .....let's add a User to this Group and see what happens:





I have shared this idea with FX Sir and he will soon be incorporating this stuff in his Monitor AD Script ...Monitor his blog for the new version of the Monitor AD Script ;)

This will be really cool as now you won't have to setup a task in Scheduler which runs every minute....Whenever there is a change it will just trigger the Script......awesome !

It is pretty amazing what you can achieve when you start using WMI Eventing...

Try out this stuff....you can make a lot of changes to the Script on your own and use it.

Let me know if you run into any problems !!


~Regards~

Saturday, January 18, 2014

Calling PS1 from VBS and gotchas with cmdletbindingattribute() - Part 2

This post is in continuation of my previous post where things got interesting while trying to execute a PS1 from a VBScript which will start/stop the website.

At the end of post there was this interesting case where we tried to stop a website which did not exist ...But we would always get a Return code of 0 ...or Success message.

Now that is bad for a Production Script as it should fail if we didn't give the correct website name.

So for this post my sample VBScript will remain almost same with only one modification.....on the website name (in Bold Red below):

Set objShell = CreateObject("WScript.Shell")
strCommand = "powershell.exe -noprofile -file C:\temp\test2.ps1 -websitename WebSiteDoesnotExist -action stop -verbose"

strError = objshell.run(strCommand,1,True)
If strError then
                        
    WScript.Echo "Failed with Exit Code" & strError & "."
else
    WScript.echo "Success"
endif 

So what could be the issue...So I tried to execute a simple PS1 first to check if it returns back the proper Exit code to the caller VBS. I used the below PS1 for testing:


Now Let's run this PS1 from the sample VBS (have to change the path of the PS1 to C:\temp\Dex1.ps1 )




Hmmm...still success which means a zero exit code is being returned.
Now let's try a very very minimal PS1 with one line (named it Dex2.ps1)



Let's give this a run:


Voila ! that works.......So I went back tried a few things and it appears if I use the cmdletbinding() attribute to make this an advanced function it doesn't work. Strange cause I tried the same thing in my Windows 7 machine and it works.

So at last below is my machine specs where I was trying this out:




At the end would just say that PowerShell sometimes behaves in an erratic way....but there is a way out, always and in the process you might learn something new :)





Thursday, January 16, 2014

Calling PS1 from VBS and Web-Administration without the PowerShell Module - Part1

Calling PS1 from VBS
Recently I got this strange request to call PowerShell Script from VBScript... Yeah!! I am serious.

Well the scenario is that we have a main VBScript which calls other VBScripts to perform Application Deployments on  Servers.

In this main VBScript we want to add the functionality to start/stop websites and webapppools as these need to be stopped while deploying the web apps on the server. So to add this functionality to our VBS we want to use PowerShell Scripts.

Learning (skip these for now will make more sense after reading the post):



This task seemed to be easy enough at first ...

Sample VBScript 1:

Set objShell = CreateObject("WScript.Shell")
strCommand = "powershell.exe -noprofile -file C:\temp\tes2t.ps1 -websitename DexTestWebsite  -action stop "

strError = objshell.run(strCommand,0,True)
if strError then
                        
    WScript.Echo "Failed with Exit Code" & strError & "."
else
    WScript.echo "Success"
endif


Looks easy enough...we create a WScript.Shell Object and then we just use the run() method on it to run the powershell.exe and wait for it to complete and get back a return code from the process for more details see the documentation of the method here . The VBS will display the exit code returned in case of a failure.

Sample PowerShell Script (test2.ps1)


So my Sample VBS will call this Sample PowerShell Script to stop a website named "DexTestWebsite".....See below :




Well seeing this I knew I had a huge problem at hand...First of all correct Exit code not being sent back and second there was some problem with the Script not able to start/stop the website.

So here is what I did.....I just executed another sample VB Script which opens up a PowerShell Console.

Below is the Sample VBScript 2:

Set objShell = CreateObject("WScript.Shell")
strCommand = "powershell.exe -noexit"

strError = objshell.run(strCommand,1,True)


I can then interact with the PowerShell Console create by above VBS and see what happens when I try to Import the WebAdministration Module and run Get-Website cmdlet :


That's a bummer...cause everything works on a normal PowerShell instance (blue window on right).

So I have identified one part as to why this fails....WebAdministration Module can't be used......This is proving to be not so easy after all.

So I googled around and came up with below Script which runs perfectly fine in the PowerShell instance spawned by the VBS ...you can check that by running it on that PowerShell instance:


Below is just the sample of the Script:


Note : You might want to modify above Script if you are looking to use.
Just as a proof that the above Script works for me..below is a screenshot:

So now the big moment...Try calling the first Sample VBScript 1 , Now we will be using the new PS1 (which doesn't use the WebAdministration PowerShell Module). Refer below Screenshot:

Phewwww ! Finally........It works !

But wait there's more which will make you scratch your head.
Suppose to our Sample VBScript 1 we make small modification....like changing the website name to "WebsiteDoesnotExist" and run the VBS...it should report back a failure right. Below is the modified Sample VBScript 1:

Set objShell = CreateObject("WScript.Shell")
strCommand = "powershell.exe -noprofile -file C:\temp\test2.ps1 -websitename WebSiteDoesnotExist -action stop -verbose"

strError = objshell.run(strCommand,1,True)
If strError then
                        
    WScript.Echo "Failed with Exit Code" & strError & "."
else
    WScript.echo "Success"
endif


Ok Let's run this....but let me assure you ...I don't have a website running by that name , So when I run the VBS above:

This keeps getting interesting .......
So after a lot of head-scratching and countless hours of staring at the code and lot of testing....I was able to make this work and I will discuss that in next post....this one is exceeding the "Boring" limit already :P

See ya all soon.

P.S. - I was testing all of this on a Windows Server 2008 R2 machine with IIS 7.5.7600.16385 running and PowerShell version 2.


----------------------UPDATE ------------------------
I tried to reproduce this on my Server 2012 Lab ....But couldn't.
The WebAdministration PowerShell Module works fine on it.


Saturday, January 11, 2014

SCCM 2007 + PowerShell -- Final Post

This is the last post on the SCCM 2007 + Automation Series in this blog.
I will share the Script with which I was able to achieve automation of the Query Based Deployments.


Finally using this Script I can Add machine names to the Query Membership Rule in a Collection...thus freeing myself of the GUI clicks needed in ConfigMgr Console.

So I have a test collection by the name "Deepak test 1" and what I have done is removed all the Query Rules from it to show how the Script works.
So Initially the collection has no Query membership rules, as evident from below screenshot of the Collection Properties in the ConfigMgr07 console:




And below is a count of no of members inside the collection...No members



I will be using the SCCM-Commands PowerShell module for my Script , I did cover some parts of it in previous post

So I will describe the logic quickly.....I have a Function named Add-MachineToSCCMQueryRule which is the Function which will add specified machine name to the Collection (Need to Input Collection ID).

To not screw up exisiting Query MemberShip Rules on a Collection..the function creates a new Query Rule by the name "Dexter_TEST_QueryRule" (you can change that inside the Source).
Note that the above Query Rule is created only the first time and subsequent times the machine names are added to this Query Rule.


So once we have the Query Rule in place...then we get do some text manipulation add the machine name to the Query Expression .

To update the query we have to delete the Old Query Rule and Add the new updated one.....There is not method to updated the QueryRule (only add & remove operations).

The code is very self explanatory and available for download from the Technet Repository

Apart from adding the computernames to the collection Query Rule the Script will refresh the machine  policy at the end and check if necessary SMS related services are working on the client.....and try to fix them.



The Query Expression on the Query Rule is of the below format at the beginning (Notice the "null")...You can change that in the Source:
'select SMS_R_SYSTEM.ResourceID,SMS_R_SYSTEM.ResourceType,SMS_R_SYSTEM.Name,SMS_R_SYSTEM.SMSUniqueIdentifier,SMS_R_SYSTEM.ResourceDomainORWorkgroup,SMS_R_SYSTEM.Client from SMS_R_System where SMS_R_System.NetbiosName in ("null")'

The machine names are appended to it at the end. So if machine name "server01" is added to the Query Expression then it would change to:
'select SMS_R_SYSTEM.ResourceID,SMS_R_SYSTEM.ResourceType,SMS_R_SYSTEM.Name,SMS_R_SYSTEM.SMSUniqueIdentifier,SMS_R_SYSTEM.ResourceDomainORWorkgroup,SMS_R_SYSTEM.Client from SMS_R_System where SMS_R_System.NetbiosName in ("null","server01")'

So after getting the function loaded into my session (by dot sourcing) in PowerShell...Below is how you use it
PS C:\> Add-MachineToSCCMQueryRule -SCCMServer sdwpsms012 -CollectionId DEX123BD -computername Win7Test01,Win7Test02 -Verbose
VERBOSE: Add-MachineToSCCMQueryRule: Starting the function
VERBOSE: Add-MachineToSCCMQueryRule: Module SCCM-Commands already imported
VERBOSE: Add-MachineToSCCMQueryRule: Successfully connected to the SCCM server
VERBOSE: WMI Query: SELECT * FROM SMS_Collection WHERE CollectionID='DEX123BD'
VERBOSE: Add-MachineToSCCMQueryRule: Queried the Collection Deepak Test 1 with CollectionId : DEX123BD  successfully
VERBOSE: Collecting rules for DEX123BD
VERBOSE: Add-MachineToSCCMQueryRule: Queried the QueryRules on the Collection successfully
VERBOSE: Add-MachineToSCCMQueryRule: Dexter_TEST_QueryRule not found for this collection..creating one
VERBOSE: Add-MachineToSCCMQueryRule: Dexter_TEST_QueryRule Created and saved for the Collection
VERBOSE: The machine name Win7Test01 is not in the QueryRule.....adding it
VERBOSE: Win7Test01 seems to have Client Installed
VERBOSE: SCCM Client on the Win7Test01 is Active
VERBOSE: The machine name Win7Test02 is not in the QueryRule.....adding it
VERBOSE: Win7Test02 seems to have Client Installed
VERBOSE: SCCM Client on the Win7Test02 is Active
VERBOSE: Taking backup of the QueryExpression to C:\Temp\QueryBackup.txt...just in case
VERBOSE: Invoked Method DeleteMembershipRule on the Collection
VERBOSE: Invoked Method RequestRefresh on the Collection
VERBOSE: Invoked Method AddMembershipRule on the Collection
VERBOSE: Invoked Method RequestRefresh on the Collection


SCCMClient        : Yes
Action            : Added
Computername      : Win7Test01
QueryRuleName     : Dexter_TEST_QueryRule
CollectionId      : DEX123BD
CollectionName    : Deepak Test 1
SCCMClient_Active : Yes

SCCMClient        : Yes
Action            : Added
Computername      : Win7Test02
QueryRuleName     : Dexter_TEST_QueryRule
CollectionId      : DEX123BD
CollectionName    : Deepak Test 1
SCCMClient_Active : Yes

VERBOSE: Add-MachineToSCCMQueryRule: All the machines added..now doing post advertisement tasks
VERBOSE: Win7Test01 --> is online doing Policy Refresh and Service Check
VERBOSE: Invoke-SCCMServiceCheck_Fix : Win7Test01 --> Automatic Updates Service Checked
VERBOSE: Invoke-SCCMServiceCheck_Fix : Win7Test01 --> Windows Management Service Checked
VERBOSE: Invoke-SCCMServiceCheck_Fix : Win7Test01 --> Remote Registry Service Checked
VERBOSE: Invoke-SCCMServiceCheck_Fix : Win7Test01 --> SMS Agent Host Service Checked
VERBOSE: Invoke-SCCMServiceCheck_Fix : Win7Test01 --> BITS service Checked
VERBOSE:  Invoke-MachinePolicyRefresh: Win7Test01 --> Machine Policy refreshed :) 
VERBOSE: Win7Test02 --> is online doing Policy Refresh and Service Check
VERBOSE: Invoke-SCCMServiceCheck_Fix : Win7Test02 --> Automatic Updates Service Checked
VERBOSE: Invoke-SCCMServiceCheck_Fix : Win7Test02 --> Windows Management Service Checked
VERBOSE: Invoke-SCCMServiceCheck_Fix : Win7Test02 --> Remote Registry Service Checked
VERBOSE: Invoke-SCCMServiceCheck_Fix : Win7Test02 --> SMS Agent Host Service Checked
VERBOSE: Invoke-SCCMServiceCheck_Fix : Win7Test02 --> BITS service Checked
VERBOSE:  Invoke-MachinePolicyRefresh: Win7Test02 --> Machine Policy refreshed :) 
VERBOSE: Add-MachineToSCCMQueryRule: Ending the function

So you just call the Function pass it the SCCM server name , CollectionId and the machine names to be added to your Query Rule.

Note: I choose to hard-code SCCM Server name and the Template of the Query in the Script Source so that I don't have to specify it each time and not to confuse my other colleagues.



So after doing this if you go to the ConfigMgr Console...and select the test collection (you will need to hit F5 to update the view if console is already opened to see the members)

Now see below the Query Rule appears...Voila !!



And you can see the count now too:



So this solution makes adding the machine names easy to the collection, thus reducing my job of deploying applications. 

To add more awesomeness to the Script ....it will pop-up a warning if the computernames specified don't have a SCCM client installed or SCCM Client not active on them.

I wrote this Script to work in PowerShell v2.
So in future am going to add CIM sessions and a couple of things to it too. Let me know your feedback if any.Download from the Technet Repository

Monday, January 06, 2014

PowerShell Validation Attributes -- validates in assignment

Validation Attributes in PowerShell ...Eh! I use them like everyday when I script.......But I noticed this thing recently.

So suppose I have a sample function below:

Now as you see it's nothing fancy..so just run it


PS C:\> Test-ValidateRange -Param1 8
Param1 = 8
The variable cannot be validated because the value 11 is not a valid value for the Param1 variable.
At line:19 char:5
+     $Param1 = 11
+     ~~~~~~~~~~~~
    + CategoryInfo          : MetadataError: (:) [], ValidationMetadataException
    + FullyQualifiedErrorId : ValidateSetFailure
 
Param1 = 8

PS C:\>

It seems that the validation attribute not only just run at the time when you first call the function ..but once you have defined a validation attribute on a parameter then through the lifetime of that variable/parameter if you do assign a new value to it then the validation attribute run against it ..thus guaranteeing that it has the value you wanted.

To better see it you can save the file put a break point at the assignment operation being done in above code and then step one line at a time....It's cool once I have validation attributes in place....then PowerShell ensures that the variable has a value which adheres to them .....Cool !

There is another very cool thing -- you can use the validation attributes with the variables in your code (only in PowerShell v3). Read the article here in the PowerShellMagazine