Monday, January 4, 2010

How to use PowerShell v2 RTM and the ActiveDirectory Module that comes with RSAT.

I noticed that not a lot of people are using the new Active Directory module in PowerShell v2 RTM (ps2rtm).  Maybe it's because you can only take advantage of the AD module if you are running Windows 7.  Windows 7 with AD module and ADWS will let you manage Win 2003 AD but you cannot run the PowerShell AD module on Win XP or 2003. 

I had been avoiding PowerShell until recently for the more familiar (in my case) vbscript for several years now.  I just didn't want to give up vb.  [adsi] in PowerShell didn't seem that much easier than adsi in vbscript.  However, recently a catastrophic event took place in my life (all my work in vb for the last year were gone) which caused me to re-evaluate PowerShell.  I'd been putting it off but now forces were at work that kind of shoved me in that direction.  So I dove in.  I have to say--I am so glad I did.

That said I'm finding that a large majority of the posh community are not using the latest greatest.  I can understand that.  Had I invested a couple of years in ps1 I would be reluctant to move to ps2rtm for production stuff (look at me I didn't even want to move away from vbscript).  Plus I was reluctant to move to Windows 7.  All my colleagues were using Win7 RCx not sure which version.  I waited until RTM and then only installed it on an extra workstation in my office.  Now I run it on my primary workstation--it really is a nice OS.

Anyway, I just wanted to say to moving to ps2rtm and using the new ActiveDirectory module is not that painful (if you are running Windows 7).  It takes all of 15 minutes.  I'm not trying to diminish the value of Quest tools, but really the future of ps2 includes a fully functional, fully featured set of AD tools.  And it's really not that hard to start taking advantage of them (even if you are running a Windows 2003 functional AD environment).

First prepare your AD environment.  Here are the instructions and download.  Read the page carefully.  For Windows 2003 DCs you will need to install two patches (the links for the patches are in the instructions on that page) and you may need to install the latest .Net 3.5 from Microsoft Update site.  Again all of the instructions and prerequisites are found on the page I linked to above.  I'm running a Windows 2003 native AD environment on DCs with Windows 2003 SP2 (no R2 in my environment) and ADWS works great.  You only need one DC running ADWS in each domain.

Next install the RSAT tools in Windows 7.  Installation and instructions are here.  After installing the RSAT tools you need to enable the AD features.

If you have prepared your environment according to the instructions, then, after starting PowerShell enter the command import-module activedirectory

enter get-help *-ad* or get-command -Module ActiveDirectory to see all of the AD functionality that the ActiveDirectory module gives.

Sorry Windows XP users or Windows 2003 server users.  The ActiveDirectory module is not (as of yet) available for those systems.  I saw a number of forums with complaints about that but with no response so not sure if MS will ever make the module available for those systems.

Have fun.

Wednesday, December 30, 2009

WPK--Tough to learn but very rewarding.

Well I've been trying to figure out how to build an app with WPK (Windodws Presentation Framework PowerShell Kit).  James Brundage has some really nice videos you can check out here.  The main site for WPK is here (which is really the home site for the PowerShellPack).  WPK is just one module that comes in the PowerShellPack.

The reason I say "Tough to learn..." is due to the general lack of good documentation.  For example if I get-help new-textbox I get all of the different parameters all jumbled up together.  If I use the -full switch on get-help it displays all of the parameters individually, but there is no explanation on how to use the parameters or what kind of data to feed the parameters (the jumbled parameters in some cases show what type of data the parameter is looking for).  So within PowerShell's help system (outside of finding parameters) it becomes difficult figuring out how to use the parameters.  For example one parameter on almost all WPK new-* cmdlets is the -DataBinding parameter, but there is nothing within PowerShell that explains how to use that parameter.  If you look online, the data you feed that parameter is very complex.  So then you go to the WPF MS site and try to interpret C# into PowerShell because none of the examples on the WPF site show how to use PowerShell cmdlets.  For me it turned into a guess fest.

  
So, yes, tough to learn.  However, after ton's of digging today, I found very rewarding what I can actually do with WPK once you know how it works.  It'll be nice to see some good documentation in one place in the future.  If someone is aware of a such a place please let me know.

Anyway, I started playing around with it today and had problems referencing other objects inside the main window.  For example if I wanted to change the content of a label with the text from a TextBox, I was finding it difficult to do.  I found a bunch of information on -DataBinding (which I have to say was at a minimum confusing).  Again, there is so little documentation on WPF as it relates to PowerShell cmdlets.  I spent hours trying to make the text on one TextBox equal to the text in another Textbox.  Well it ends up that the best place to find answers are the examples that get installed when you install the PowerShellPack.  I found the examples on my %systemdirve%\Users\%username%\My Documents\WindowsPowerShell\Modules\WPK\Examples.

Here is a quick script that sort of emulates a chat window.


New-Grid -Name gGrid1 -Background  blue -Rows 2 -Columns "auto","1*" -On_Loaded {            
    $script:AccData = $window | Get-ChildControl AccData            
    $script:Data = $window | Get-ChildControl Data            
    $AccData.background = "black"            
    $AccData.foreground = "lime"            
} -Children {            
    New-Label "Data" -Row 0 -Column 0 -Foreground "yellow"            
    New-TextBox -TextWrapping Wrap -AcceptsReturn -Row 0 -Column 1 -Name Data -On_PreviewKeyUp {            
        if($_.key -eq "Return"){                      
            $AccData.text = $AccData.text + $this.text            
            $this.text = ""            
        }            
    }            
    New-Label "Accumulated Data" -Row 1 -Column 0 -Foreground "yellow"            
    New-TextBox -IsReadOnly -TextWrapping Wrap -Row 1 -Column 1 -Name AccData            
} -asjob
Of course I'm running PowerShell v2.0 RTM on Windows 7.  I had to install the PowerShellPack for this to work.

Have fun.

Thursday, December 17, 2009

Remove a user in domainA from a group in domainB

I'm writing a script in Posh (that seems to be the popular acronym for PowerShell--so I'll go with it as well) that finds all of the inactive users in the domain and moves them to an OU to be disabled one month later if the manager of the user doesn't respond to the email that the script sends.  I finished that part and and now I'm at the point that I disable user accounts where there was no response.

The moving and disabling the users is rather straight forward in Posh. The problem I encounterd was removing the group memberships of the users (a business requirement when we disable the user account).  Now removing the group memberships of groups in the same domain as the user account is also very straight forward, but what happens when you try to remove the user account from a group that is located in a sibling domain in the same forest.
Well here's what happens:

Remove-ADGroupMember : Cannot find an object with identity: 'CN=Smith\, John,OU=Users,OU=location,DC=DomainA,DC=example,DC=com' under: 'DC=DomainB,DC=example,DC=com'.
At line:1 char:21
+ Remove-ADGroupMember <<<<  -Identity $group.ObjectGUID -Members $group.member[12] -Credential $myAdminCreds.DomainB -server DomainB.example.com
    + CategoryInfo          : ObjectNotFound: (CN=Smith\, Joh...,DC=example,DC=com:ADPrincipal) [Remove-ADGroupMember], ADIdentityNotFoundException
    + FullyQualifiedErrorId : SetADGroupMember.ValidateMembersParameter,Microsoft.ActiveDirectory.Management.Commands.RemoveADGroupMember

 All in nice bright red wording.

Here is a script similar to the one that generated that error:

Import-Module ActiveDirectory

$adminCreds = @{domainA = Get-Credential "domA\admin";domainB = Get-Credential "domB\admin"}

$user = Get-ADUser johndoe -Properties memberof -Credential $adminCreds.domainA
$groups = $user.memberof | where{$_ -match "dc=domainB"}

foreach($group in $groups){
    Remove-ADGroupMember -Identity $group -Members $user.distinguishedName -Server domainB.example.com -Credential $adminCreds.domainB
}

Had I chosen groups from domainA, the script would have run without error.

So I posted my problem on every forum I could find including 'The Official Scripting Guys Forum!' as well on #powershell irc chat on freenode.net.  To date no one has responded.  So I delved into a work-a-round.

At first I tried straight up ADSI:

$group = [adsi]"LDAP://$($group.distinguishedName)"
$group.putEx(4,"member",@($user.distinguishedName))
$group.setinfo()

However, because my account is not an administrator in the sibling domain I got an access denied error.  I did a bunch of research again and low-and-behold the best I got was that there is no specific way to provide credentials to a foreign domain with [adsi] (albeit a sibling domain in the same forest).  I even mapped a drive to the domain controller in domainB using my admin credentials in that domain--nadda!

So after a ton of unfruitful research I developed my own work-around. If one of you Posh gurus wants to post an easier way I'd be delighted.  I use WinRM 2.0 to accomplish this (I read a ton of help pages):


Import-Module ActiveDirectory

$adminCreds = @{domainA = Get-Credential "domA\admin";domainB = Get-Credential "domB\admin"}

#Get the user object
$user = Get-ADUser johndoe -Properties memberof -Credential $adminCreds.domainA
#Find groups with distinguishedNames that are in domainB
$groups = $user.memberof | where{$_ -match "dc=domainB"}

#Create a remote session on domainB's domain controller
$s = New-PSSession -ComputerName domb-dc1 -Credential $adminCreds.domainB

<#Unfortunately you can't just reference variables in your local ps session while in the remote session so some setup 
work is needed.  Cycling through the collection of group DNs you first create a hash table (I love hash tables) with 
the parameters you will need to work with in the remote session.#>
foreach($group in $groups){
    $LHparams = @{userDN = $user.distinguishedName;groupDN = $group} #Local Host parameters.
    <#We need to send those parameters to the remote session.  The variable '$multivars' is an arbitrary name. 
    You can use what ever you makes sense in that spot. 'RHparams' Remote Host parameters will be set in the remote pssession.
    $multivars becomes $LHparams and can be used to set a variable on the remote host.  
    
    You can send multiple arguments and must have multiple parameters (param($a, $b))to hold those arguments.
    So this would work:#>
    
    Invoke-Command -Session $s -ScriptBlock {param($rhuserDN, $rhgroupDN) $userDN = $rhuserDN;$groupDN = $rhgroupDN}` 
                                                            -ArgumentList $user.distinguishedName,$group
    
    #However, I like hash tables better-well actually I love hash tables (best thing since sliced bread). so:
    
    Invoke-Command -Session $s -ScriptBlock {param($multivars) $RHparams = $multivars} -ArgumentList $LHparams
    
    <#Now you may be tempted to enter the session in your script--but don't.  My previous post had the script doing that but it
    doesn't work.  The commands issued in the script after entering a PSSession don't do anything.  So do this 
    (check example 5 of Help invoke-command -examples):   #>
    $command = {
        $group = [adsi]"LDAP://$($RHparams.groupDN)"
        $group.putEx(4,"member",@($RHparams.userDN))
        $group.setinfo()
    }
    <#Note that when using the hash table in the remote session the porperties ARE case sensitive.  
    GroupDN gets you nothing groupDN has the data.#>
    
    <#Of course we want as few commands as possible so let's not run the invoke-command twice
    instead let's do this:  #>
    
    $LHparams = @{userDN = $user.distinguishedName;groupDN = $group}
    $command = {
        param($multivars)
        $RHparams = $multivars
        $group = [adsi]"LDAP://$($RHparams.groupDN)"
        $group.putEx(4,"member",@($RHparams.userDN))
        $group.setinfo()
    }
    
    #Now send the commands to the remote session just once.
    Invoke-Command -Session $s -ScriptBlock $command -ArgumentList $LHparams
}

"***The script looks like this without all the extraneous comments***"

#Provided for easier reading of the code.

Import-Module ActiveDirectory

$adminCreds = @{domainA = Get-Credential "domA\admin";domainB = Get-Credential "domB\admin"} #Hash table woohoo!!!
 
$user = Get-ADUser johndoe -Properties memberof -Credential $adminCreds.domainA
$groups = $user.memberof | where{$_ -match "dc=domainB"}

$s = New-PSSession -ComputerName domB-dc1 -Credential $adminCreds.domainB

foreach($group in $groups){
    $LHparams = @{userDN = $user.distinguishedName;groupDN = $group} #another hash table woohoo woohoo!!!
    $command = {
        param($multivars)
        $RHparams = $multivars
        $group = [adsi]"LDAP://$($RHparams.groupDN)"
        $group.putEx(4,"member",@($RHparams.userDN))
        $group.setinfo()
    } 
    Invoke-Command -Session $s -ScriptBlock $command -ArgumentList $LHparams
}

Tuesday, December 15, 2009

LastLogonTimeStamp

Hey VBScripters,

Do you like working with LastLogonTimeStamp (remember this):


Set oUser = GetObject("LDAP://" & sUserDN)
Set oLastLTS = oUser.Get("lastlogontimestamp")
iLastLogon = oLastLTS.HighPart * (2^32) + oLastLTS.LowPart
iLastLogon = iLastLogon / (60 * 10000000)
iLastLogon = iLastLogon / 1440
dLastLogon = iLastLogon + #1/1/1601#



Here is how this works in Powershell v2 RTM using the ActiveDirectory module:

$ADUser = Get-ADUser johndoe -Properties lastlogontimestamp
if ($ADUser.lastlogontimestamp -ne $null){
        $lastlogon = Get-Date -Date ([DateTime]::FromFileTime([Int64]::Parse($ADUser.lastlogontimestamp))) -Format MM/dd/yyyy
    } else {
        $lastlogon = Get-Date -Date $ADUser.created -Format MM/dd/yyyy
    }

I think that is just so elegant.  If I hadn't added the checks and the formatting (like I didn't do in the VB script) this would have only taken two lines.  Cool huh?

Have fun,
Cameron out.

Get Latest File function

Our HR department dumps an employee file that contains the latest listing of employees in the company to the same folder each week. The file contains their employee ID, their Manager, and their manager's employee ID. The following function allows me to get the lastest file from any folder in the last x number of days.
function Get-LatestFile([string]$FilePath, [int]$NumDaysAgo){
    $DateToCompare = (Get-date).AddDays(-$NumDaysAgo)
    return get-childitem $FilePath | where-object {$_.lastwritetime –gt $DateToCompare}
}
Use the function as follows:

$LatestFile = Get-LatestFile "\\server\share\" 7

So basically you supply the folder location and the time frame.  The above command will look for the latest file in the past seven (7) days in the location specified.

PowerShell Blogging

Well there are a number of really good resources out there for PowerShell and I've been using them frequently. However, I've noticed that not a lot of people are using PowerShell v2 RTM that comes with Windows 7 to manage Active Directory. I'm hoping this can become a place where AD and PowerShell V2 can come together to help administrators take advantage of some of the exciting technologies that are available. I'm new to blogging and I'm a 'newbie' with PowerShell. I've spent the last decade VBScripting all over the place now I'm really focused on learning and taking advantage of all the power PowerShell has to offer.

I have to say to all you VBScripters. PowerShell is really a superior scripting platform. I didn't believe it at first but over the past few weeks I'm convinced that I took way too long to come on over to the PowerShell side of scripting.

My problems with PS were:
  1. I don't like curly brackets {}.
  2. I didn't like putting $ in front of my variables.
  3. I really thought I had to pipe everything. Actually, you don't have to pipe anything if (like me) you don't find that sort of thing intuitive. You can write straight script just like you would in VBScript.
  4. Almost forgot--I really didn't like that comparison operators like = < > were replaced with -eq -lt -gt.
Those things may seem silly to some, but an honest evaluation of my reasoning produced that list as to why I refused to switch to Posh sooner. Now that I've been using it for about three weeks I'm hooked. It really is a superior scripting language. I find myself being able to do all of the things I did in VB plus more and in a more elegant way. I'm glad I switched.

If like me you have problems with the syntax, I can tell you that if you just start using PowerShell you will get used to the syntax fairly quickly. Now curly brackets don't bother me one bit and I'm already used to the $ variables. So trust me, start using PowerShell, you'll love it.

I figure with this blog I'll post some of my projects and share what I learn as I learn it. Hopefully, it'll be useful to someone out there.

Happy scripting...

Cameron Out.