An Administrator's guide to PowerShell

Saturday, January 6, 2007

A Powershell Adventure: Chpt2 ‘Using .NET in Powershell'

Using .NET in Powershell (admins view)
------------------------------------------------------------
One of the best features of POSH is the direct access to the .NET Object model. Unfortunately... this was one of my biggest obstacles. I’m not a developer and most of the concepts of programming are somewhat foreign to me.

When I started learning POSH I didn’t know the difference between Classes, Interfaces, constructs, or members and I didn’t truly understand the concept of objects (from a programmers point of view.) I think these concepts are critical to learning and using .NET with POSH. I also found that knowing a little c# (at least be able to read it) has become incredible helpful.

I would like to try to explain these concepts without getting to developery. As in all the things I post.. I like to explain by examples so I will be providing some code to help me through the process. I will also try (with my limited ability) to give some basic guidance in C# > POSH translation (this is critically helpful as most .NET examples are written in c#.)

Ok... Lets start with Definitions. Again.. if you’re a Developer... Please feel free to comment on anything I may not be exactly right on. These definitions are my opinion on what these concepts entail.

Definitions
===========
.NET: Blackboxed code that accepts specific input and returns either a value or object. Basically, Microsoft did all the coding for you. You just have to call on it correctly... MSDN is invaluable resource for this.

Wiki - http://en.wikipedia.org/wiki/Microsoft_.NET_Framework

Class: Almost everything I reference in .NET is a class. I like to think of a class as a template for an Object. A class is definition of what an object should look like. What properties/methods it should have. For Example... a Microsoft.Win32.RegistryKey object should have Name Property and a GetValue method.

Wiki - http://en.wikipedia.org/wiki/Class_%28computer_science%29

Members: Every class has members. Members are the properties and methods combined. It is a good place to look if you just want to see and overview of what a class has to offer. Check this out.
http://msdn2.microsoft.com/en-us/library/microsoft.win32.registrykey_members.aspx

Properties: Properties are define by a class as attributes of an object. Microsoft.Win32.RegistryKey class has properties of Name, SubkeyCount, and Value Count. So every Microsoft.Win32.RegistryKey object can have those properties.

Wiki- http://en.wikipedia.org/wiki/Property_%28programming%29

Methods: Methods are also defined by the class but instead of an attribute its more like a function of the class. Microsoft.Win32.RegistryKey class has CreateSubKey, DeleteSubkey, SetValue, and so on. So... just like properties every Microsoft.Win32.RegistryKey object can have those methods.

Wiki - http://en.wikipedia.org/wiki/Method_%28computer_science%29

Constructor: I define Contructs as the information or objects required to create an instance of a class. Lets use System.Data.SqlClient.SqlCommand as an example. It has 4 different ways you can create an object from the class. Each way creates and object with slightly different data.
Ref: http://msdn2.microsoft.com/en-us/library/system.data.sqlclient.sqlcommand.sqlcommand.aspx

Wiki - http://en.wikipedia.org/wiki/Constructor_%28computer_science%29

Static Methods: These are just like methods, but are availible without having to create an instance of the object. The reason I single these out is because in Powershell... accessing static functions is really simple. All you have to do is to [.NET Class]::StaticMethods("arguments"). Here is an example [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey.

Now that you know that...

Lets look at some examples of .NET use in POSH.
Lets look at Eventlog access using .NET
PS C:\> $evtLog = new-object system.diagnostics.eventlog
PS C:\> $evtLog.Log = "Application"
PS C:\> $evtLog.Entries
Index Time Type Source EventID Message
----- ---- ---- ------ ------- -------
1350 Nov 19 15:54 Warn Alert Manager Eve... 257 VirusScan Enterprise: Would be blocked by behaviour blocking
1351 Nov 19 15:54 Warn Alert Manager Eve... 257 VirusScan Enterprise: Would be blocked by behaviour blocking


Lets look at a couple of .NET static method Calls

Lets call Now method of System.DataTime
PS C:\> [system.datetime]::now
Monday, January 08, 2007 7:31:31 PM

Lets call GetLogicalDrives method of System.Environment
PS C:\> [System.Environment]::GetLogicalDrives()
A:\
C:\
D:\
E:\
F:\


One last one... System.Math. We will use the static method pow. What is 83 to the second power anyway?
PS C:\> [system.math]::pow(83,2)
6889


C# to Powershell translation
C# Code taking from:
http://www.csharp-station.com:80/Tutorials/AdoDotNet/Lesson07.aspx
using System;
using System.Data;
using System.Data.SqlClient;

// create and open a connection object
conn = new SqlConnection("Server=(local);DataBase=Northwind;Integrated Security=SSPI");
conn.Open();

// 1. create a command object identifying
// the stored procedure
SqlCommand cmd = new SqlCommand("CustOrderHist", conn);

// 2. set the command object so it knows
// to execute a stored procedure
cmd.CommandType = CommandType.StoredProcedure;

// 3. add parameter to command, which
// will be passed to the stored procedure
cmd.Parameters.Add(new SqlParameter("@CustomerID", custId));

// execute the command
rdr = cmd.ExecuteReader();

// iterate through results, printing each to console
while (rdr.Read())
{
Console.WriteLine(
"Product: {0,-35} Total: {1,2}",
rdr["ProductName"],
rdr["Total"]);
}

PowerShell:
$srv = "srv1"
$db = "Northwind"
$conn = new-Object
System.Data.SqlClient.SqlConnection("Server=$srv1;DataBase=$db;Integrated
Security=SSPI")
$conn.Open() out-null

$cmd = new-Object System.Data.SqlClient.SqlCommand("CustOrderHist", $conn)
$cmd.CommandType = [System.Data.CommandType]'StoredProcedure'

# This Parameter Line This will error if Parameters
# are not accepted by Stored Procedure.

$cmd.Parameters.Add("@CustomerID","ANATR") out-Null

$rdr = $cmd.ExecuteReader()

While($rdr.Read()){
Write-Host "Product Name: " $rdr['ProductName']
}

$conn.Close()
$rdr.Close()

Ok... Now to explain a couple of differences.

I think the key part of C# to Powershell translation is understanding the Namespace. In Powershell (at least as far as I know) you are unable to include namespaces. Therefore you have Fully Qualify any .Net classes you want to us. This is not the case in C#. In C# you are able to include namespaces. i.e. System.Data.SqlClient. This makes the translation a little more complicated because you have to figure out what namespace the class is from. Lets look at This example.
conn = new SqlConnection("Server=(local);DataBase=Northwind;Integrated Security=SSPI");
How do I know what SqlConnection is? You best option is to google/MSDN it. If that is unavailble you can look that the using statements and see which make sense. In this case it is using System.Data.SqlClient.
So that in PowerShell is
$conn = new-Object
System.Data.SqlClient.SqlConnection("Server=$srv1;DataBase=$db;Integrated
Security=SSPI")


Another important issue is strongly typing. While you can strongly type in Powershell you don’t have to. In my understanding of C# you have to strongly type variables. Not only that but you have instantiate them as well. This illustrated in this line
SqlCommand cmd = new SqlCommand("CustOrderHist", conn);
This line in Powershell is (notice the fully qualified class
$cmd = new-Object System.Data.SqlClient.SqlCommand("CustOrderHist", $conn)


Summary: As I hope you can see. Using .NET in Powershell is pretty strait forward and actually if you use PowerShell at all... you use .NET regularly, but maybe not as directly. Please feel free to leave comments. Also... please let me know if anything is unclear.

Friday, January 5, 2007

Powershelling Citrix (The Good, Bad, and The Code)

I would have to say that my first real experience with POSH was specifically with Citrix. As an Admin (one of many hats as a network engineer) of over 400 Citrix Servers and 3 Farms... I tend to script a alot. I am not a real fan of the CMC (Citrix Managment Console) and I much prefer anything that does not involve a GUI.

One of my biggest problems is that the three farms are isolated from each other. That makes what ever I do 3x the work. In this aspect... POSH has been a savior. I can write the script once... and then cut/paste. It's awesome.

The Good:

If there is one thing I like about Citrix (and thats about it) is the fact they wrote a series of COM interfaces for Metaframe. These are incredibly useful but until POSH you couldn't access these from the Command Shell. POSH lets you create COM objects on the fly so I use the Metafram COM interfaces exclusively.

The Bad:

As in all things Citrix... The COM interface is quirky (do I sound bitter?) I have run into a couple of little snags that if avoided will make your life alot easier.

1) Case Sensitive: I have found in my Citrix Use that for some reason. When you use MetaFrameCOM.MetaFrameServer and Initialize it... the Server Name must be in all CAPS. Not sure why (sorry) but I know if you don't .ToUpper the Server... it failes.

2) Initialize... Initialize... Initialize: Almost all of the MetaFrameCOM component's require you to Initialize... this would normally wouldn't be a problem, but the finding the Initialization codes was a bit a of a pain. (I posted them in the appendix.)

3) For none developers passing an object as a parameter (who'd of thunk it?): So... for you C# people this is normal, but for the rest u sof scripters... not so much. You can see example of this in my Publish-CitrixApplication Function at the line $mfApp.AddServer($mfAppBinding). You have to create an App Binding Object to Add the server.

The Code:

Name: Get-CitrixOnline
Purpose: Get All Citrix Servers Currently Online (not to be confused with published)
function Get-CitrixOnline {
Param([string]$zone)
$mfzone = New-Object -ComObject MetaFrameCOM.MetaFrameZone
$mfzone.Initialize("$zone")
$servers = $mfzone.OnlineServers
$servers sort ServerName Write-Output
}
Notes: Nothing special here, but make sure you know the zone name. You can get it from the CMC, but it should be the network of the first Citrix Server (i.e. 192.168.0.0)

Name: Get-CitrixApplications
Purpose: Get Citrix Apps published on Server
function Get-CitrixApplications {
Param([string]$server)
$Server = $Server.ToUpper() # Citrix requires Server in CAPS (how odd)
$mfsrv = New-Object -ComObject MetaFrameCOM.MetaFrameServer
$mfsrv.Initialize(6,"$Server")
Write-Host "SERVER $Server" -foregroundcolor Red
Write-Host "==================" -ForegroundColor Green
If($mfSrv.Applications.Count -gt 0) {
$mfSrv.Applications %{Write-Host "Published: $($_.AppName.ToUpper())"}
}
else {
Write-Host "No Applications Published for $Server" -foregroundcolor white
}
}
Notes: Very useful for getting quick idea at what apps a server has published.

Name: Publish-CitrixApplications
Purpose: Publish Citrix App on Server
function Publish-CitrixApplication {
Param([string]$Srv,[string]$myapp)
$Srv = $Srv.toUpper()
$mfSrv = New-Object -ComObject MetaFrameCOM.MetaFrameServer
$mfSrv.Initialize(6,"$Srv")
$mfApp = New-Object -ComObject MetaFrameCOM.MetaFrameApplication
$mfApp.Initialize(3,"Applications\$myapp")
$mfApp.LoadData($true)
$mfAppBinding = New-Object -ComObject MetaFrameCOM.MetaFrameAppSrvBinding
$mfAppBinding.Initialize(6,$Srv,"Applications\$app")
if($mfAppBinding) {
Write-Host "Publishing App[$myapp] on Server [$Srv]" -ForegroundColor Green
$mfApp.AddServer($mfAppBinding)
$mfApp.SaveData()
}
else {
Write-Host "Unable To Create App Binding" -ForegroundColor Red
}
}
Notes: Thing to be careful with this one is the $mfApp.Initialize(3,"Applications\$myapp") It is important to know that "Application\$myApp" needs to be the path you see in the CMC. If you created subfolders then you need to include them like $mfApp.Initialize(3,"Applications\Lab\$myapp").

Name: UnPublish-CitrixServer
Purpose: Remove All Citrix Apps from Server
function UnPublish-CitrixServer {
Param([string]$Server)
Write-Host
$Server = $Server.toUpper()
$mfSrv = New-Object -ComObject MetaFrameCOM.MetaFrameServer
$mfSrv.Initialize(6,"$Server")
If($mfSrv.Applications.Count -gt 0) {
Write-Host "Removing All Published Applications from $Server"
Write-Host "==================================================="
ForEach($app in $mfSrv.Applications) {
$myApp = $App.AppName
Write-Host "Removing App [$myApp] from Server [$Server]"
$app.RemoveServer($Server)
$app.SaveData()
}
}
else {
Write-Host "No Published Applications for $Server"
}
}
Notes: Pretty strait forward. Remember that it removes ALL apps from the server.

Name: Remove-CitrixApplication
Purpose: Removes Citrix App from Server
function Remove-CitrixApplication {
Param([string]$Srv,[string]$myapp)
Write-Host
$AppRemoved = $false
$Srv = $Srv.toUpper()
$mfSrv = New-Object -ComObject MetaFrameCOM.MetaFrameServer
$mfSrv.Initialize(6,"$Srv")
If($mfSrv.Applications.Count -gt 0) {
ForEach($app in $mfSrv.Applications) {
If($app.AppName -eq "$myapp") {
Write-Host "Removing App [$myApp] from Server [$Srv]"
$app.RemoveServer($Srv)
$app.SaveData()
$AppRemoved = $true
}
}
}
else {
Write-Host "No Applications Published for $Srv" -ForegroundColor Red
$AppRemoved = $true
}
If($AppRemoved -eq $false) {
Write-Host "This Application not Published for $Srv" -ForegroundColor Red
}
}
Notes: Only removes specified app.

Coming Soon: Should be posting more complex Citrix scripts to Add/Remove apps. Get some basic Stats, and such.

Appendix:
---------
Typical:
MetaFrameCOM.MetaFrameServer: (6,"SERVERNAME")
MetaFrameCOM.MetaFrameAppSrvBinding: (6,"SERVERNAME","AppPath\AppName")
MetaFrameCOM.MetaFrameApplication: (3,"AppPath\AppName")
MetaFrameCOM.MetaFrameZone: ("ZONE")
------ Will be adding headers soon -----

A Powershell Adventure: Chpt1 ‘Trial by Fire’

Ok... let’s face it. I’m a procrastinator. I love to learn new things, but always seem to have hard time getting started. I don't want to bore you with a Monolog, but I think examples are always great way to explain things. So... off we go

Begin Monolog

Not too long ago I was a MS employee. I loved it. It was an incredible experience. I learned so much. I worked there three times as a consultant. As consultant I was in 95/98 phone support the first time. The next two times I was in Directory Services. In my third run as a consultant I got a blue badge opportunity on the ROSS team (Rapid Onsite Support Service.) As a ROSS guy I traveled to customers sites and fixed problems that couldn't be resolved over phone support. That said… you could imagine that these were horrifically bad problems. I will provide an example... of course due to NDA's I can’t use customer names and such.

My specialty at MS was Directory Services (AD) so normally when we had a ROSS that was DS related. I got the privilege to go. One such engagement was at a Large Customer that (at the time) had the largest AD site topology in the world. They had approximately 2900+ DC's in about 2500 sites. If anyone is interested I can post the technical problems we came across, but that is beyond the scope of this post. Long story, short... I had to rebuild their entire Replication topology... Could you imagine having to logon to 2900+ DC's to fix this? I was kicking myself for not learning VBScript sooner, but I had no choice. I had to learn it now... and quick. So... I bit the bullet and I learned VBScript (at least what I needed to know) in a week. In my first week of VBScripting, I wrote scripts to create text files for the Branch Office Deployment Guide (MS Thing.) I also had to write AD replication/FRS replication Test. It was bitter/sweet experience. LOTS of long nights, but... I FINALLY learned VBScript. It took me years to make myself do it, but it was worth it.

This brings me today... and my powershell adventure.

End Monolog

Because of that experience I decided that when powershell came out... I wasn't waiting again.

My Learning style is to "Learn by doing," but I am not much for wasting time either. So generally I will only stop to learn something if I can find an application I can use it for. I was lucky with powershell... I had a project (more on this in later post) and time frame.

My experience with Powershell has been trying, mostly due to my utter lack of Development experience. I didn’t know a class from function. Every reference to .NET was Greek. I can say that the news groups have been tremendously helpful although… to be honest… most of the frequent posters are Dev guys so I don’t get a lot of what they say (they do put up with me though… Thanks GUYS!) Jeffrey and his team have been amazing to. Like I said… I have worked at MS and it is not at all like people think. It is not some giant SOFTWARE superpower with no care of what its consumers want. The attitude at MS is very customer centric although it is hard to see that sometimes… Jeffrey’s team does a GREAT job displaying this. You can tell by their post… they truly and honestly want to make the product better.

My biggest problem with PowerShell is the learning curve for Admins. Don’t get me wrong… it is necessary, but it’s still a problem. I think this is my main motivation for this blog. I hope to somehow I can help other Admins come over to the dark side ;) I approach Powershell from an Admins point of view and I’m sure my blog(s) will push that as well. From a Dev point of view Powershell is useful… from and Admin point of view its Life Changing!

Anyway… This was WAY longer than I had intended. I will take my leave now… Stay tuned :)

Powershell, Remote Registry and You! Part 1 (Overview)

I was reading the news groups (as I do all the time) and I have notice numerous request/questions regarding remote registry access in powershell. I thought I would try to see if I could shed some light on the subject. So without further delay... on with the show!

Overview:
----------
Registry access in Posh is realatively simple and extremely powerful.
From a local stand point its as simple as:
PS> Set-Location HKLM:System
From a remote standpoint... you have to utilize the powers of .NET.
$ServerKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, ServerName)

For the purpose of this post... I am going to focus on the remote aspect. Local is cover in tons of documentation. So, cause of time, I am only going to address the .NET method.

I will start by giving you the Remote Registry Object useful Properties/Methods

Object
-------
[Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine,MachineName)

Properties
-------------
Name
SubKeyCount
ValueCount


Methods (Not all.. just the ones I use often)
---------------------------------------------------
CreateSubKey
DeleteSubKey
DeleteSubKeyTree
DeleteValue
GetAccessControl
GetLifetimeService
GetSubKeyNames
GetType
GetValue
GetValueKind
GetValueNames
OpenSubKey
SetAccessControl
SetValue


As you can see... You can do basically everything you could ever want.

Now that you have a basic idea of what the .NET provider can give you... let put it to practical use.

Examples:
----------
Purpose: Get a list of Subkeys and Values of Specific Registry Key.
$key = "SOFTWARE\Microsoft\Windows\CurrentVersion"
$type = [Microsoft.Win32.RegistryHive]::LocalMachine
$regKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($type, $Srv)
$regKey = $regKey.OpenSubKey($key)
Write-Host "Sub Keys"
Write-Host "--------"
Foreach($sub in $regKey.GetSubKeyNames()){$sub}
Write-Host
Write-Host "Values"
Write-Host "------"
Foreach($val in $regKey.GetValueNames()){$val}

Result (only showing first 10 of each:)

Sub Keys
--------
App Management
App Paths
Applets
BITS
Control Panel
Controls Folder
CSCSettings
DateTimeDynamic
DirectoryExplorer

Values
------
DevicePath
MediaPath
Unexpanded
SM_GamesName
SM_Configure
ProgramsName
ProgramFilesDir
CommonFilesDir
ProductId
WallPaperDir
MediaPath
ProgramFilesPath

-------------------------------------------
Purpose: Get the Value of each of the Values.
$key = "SOFTWARE\Microsoft\Windows\CurrentVersion"
$type = [Microsoft.Win32.RegistryHive]::LocalMachine
$regKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($type, $Srv)
$regKey = $regKey.OpenSubKey($key)
Write-Host "Values"
Write-Host "------"
Foreach($val in $regKey.GetValueNames()){
Write-Host $val.PadRight(30) -nonewline
Write-Host $regKey.GetValue("$val")
}

Result (only showing first 10:)

Values
------
DevicePath = [C:\WINDOWS\inf;C:\Drivers\Broadcom\Win2003]
MediaPathUnexpanded = [C:\WINDOWS\Media]
SM_GamesName = [Games]
SM_ConfigureProgramsName = [Set Program Access and Defaults]
ProgramFilesDir = [C:\Program Files]
CommonFilesDir = [C:\Program Files\Common Files]
ProductId = [69713-640-4031427-45876]
WallPaperDir = [C:\WINDOWS\Web\Wallpaper]
MediaPath = [C:\WINDOWS\Media]
ProgramFilesPath = [C:\Program Files]

------------------------------------------------

Summary:
-----------
As you now can see. POSH is really powerful given its .NET access to the registry. Honestly... there is virtually nothing you can't do and its easy to boot. You have complete access to Registry keys/subkeys/values. You can even Create, Delete, and evaluate Values and keys. In the future I will be sharing a function I wrote to compare Registry Subkeys between machines. That has proven to be super valuable.

Well... That about does it (at least for today :) ) I think this is a pretty good start to your POSH .NET registry adventure. I will be expanding this as I have time.

As always... PLEASE PROVIDE FEEDBACK!!! :)

Thursday, January 4, 2007

The Beginning and the End (the process to)

In my powershell journeys I have found a huge quantity of glorious abilities wrapped up in this truly POWER shell. One of these superpowers is the concept of filter/function processing. While I do not consider myself a guru in any stretch I have done a good bit of work with functions and think I have a sound grasp of this ability.

I haven't used filters that much so I will stick with what I know... functions. Oh... glorious functions! These have been the life blood of my powershell experience. I came from a VBScript back ground and have always been super anal (or lazy) about code reuse. I would always try to black box my code to be able to reuse later. While this is a good practice IMHO... it does tend to get lost sometimes... This is where I believe Powershell functions come in.

There a ton of things you can do with functions and I don't have time to discuss them all, so I am going to focus on my favorite.... Begin, Process, and End oh my!

Lets start with the basic layout of a function:
---------------------------------------------
function foo{
write-host "Hello World"
}


Ok... nothing spectacular there, but what if I want to be more specific to whom I say hello

function foo{
param([string]$name)
Write-Host "Hello $name"
}


This is nice... but still... nothing big going on. I mean seriously... what kinda scripting language cant take parameters.


function foo{
Param([string]$name)
Begin{
# Only gets process at the Beginning
# Normally include Variable creation and functions
Write-Host "Starting"
}
Process{
# Gets process for each object in the pipe (if ones exist)
if($_){Write-Host "Hello $_"}
}
End{
# Always get processed once at the end
if($name){Write-Host "Hello $name"}
}
}


Now... as trivial as this looks... this function is truly amazing. It not only has the ability to take a parameter, but it can also take a pipe (pipe is explained below.)

Like I said... functions of lots of abilities... but this is my favorite. Thanks to Jeffrey and the Team.... GREAT IDEA!!!


===============================================
pipe: A pipe is when you get the results of one command and pipe them to another... Kinda like you did in DOS or BASH, but instead of pass text... in powershell you pass objects (mostly arrays of strings.)

About Me

Montclair, NJ, United States

technorati

Add to Technorati Favorites