Archive for the ‘ PowerShell ’ Category

The Wunder of PowerShell: Posting to Wunderlist

I thought I’d mill out a quick post during lunch for any fellow Wunderlist users.  If you’re not using Wunderlist then you should look into it.  It is, by far, my favorite task manager to date (and I’ve tried most of them).  One feature that I really like is the ability to email yourself a task and have it land in your inbox.  This is a feature available in some of the other task managers out there as well, so the code below could be refit to work with other systems.  Specifically, I wanted a way to quickly shoot off a task to my Wunderlist inbox from PowerShell.  To do this, I added the following function to my profile.ps1 file:

function WUNDER
{
    PARAM([string]$body)
   
    $to = "me@wunderlist.com"
    $from = bsweeney@sweeneyops.lab
    $subject = " "
    $smtpserver = "mail.sweeneyops.lab"
   
    send-mailmessage -to $to -from $from -body $body -subject $subject -smtpserver $smtpserver
}

Now, whenever I want to send a task to myself, I simply type the following from PS:

PS C:\> wunder "Some task I want to perform"

And it will be in my inbox waiting for me to sort into a task list the next time I check Wunderlist.

Managing DNS using PowerShell

One of the things I have found sorely lacking in PowerShell 2.0 is a good way to manage DNS.  I’m hoping there is a fix for this in Windows Server 2012 and PowerShell 3.0, but for now I’ve had to search for alternatives.  Now, the Scripting Guys have published an excellent article on how to manage DNS through a combination of PowerShell and WMI.  You can find it here.   As I said, this is an interesting approach, and certainly works, but I personally found it to be overly complicated.  Instead, I found that leveraging PowerShell’s ability to incorporate pre-existing tools into my scripts presented me with a more viable solution. Specifically, I’ve combined DNSCMD with PowerShell.  Here’s how it works. 

DNSCMD is a well documented and very powerful tool for managing your windows DNS server from the command line.  It can be called directly from within PowerShell. So lets say I want a script that will set up new primary DNS zones for me based on a standard that we’ve developed.  We could set up AD integrated zones this way as well, but in my situation I was dealing with primary and so that’s what I’m sticking with now.  If you want to use AD integrated zones instead then the syntax is pretty easy to modify.  So, lets say I want to set up a new zone for sweeneyops.com.  I could simply run my script like this:

./create-primarydnszone.ps1 sweeneyops.com

… and voila! The zone would be created as per my predefined standards. What are my standards?  I guess we should probably define those now:

  • I want to create a primary dns zone
  • I want to use the default windows dns zone naming convention. (e.g. if my zone is sweeneyops.lab then my zone file would be sweeneyops.lab.dns)
  • My server host name is Server1, but it is recognized publicly as ns1.sweeneyops.lab.  This server only hosts primary zones. 
  • I have a second server called Server2.  It is publicly recognized as ns2.sweeneyops.lab.  It will host only seecondary copies of the zone, and I want it listed as a name server.
  • The host name will be added automatically to the name servers list. I want to remove it.
  • I want the zone contact to be me@sweeneyops.lab.  In DNS zone syntax that equates to me.sweeneyops.lab.
  • I want to use the windows default settings for the start of authority.
  • I want to allow zone transfers, but only for servers listed in the name servers tab of the DNS zone properties.
  • I want to create an A record for WWW and an empty A record (DNSCMD thinks of this as an @ record) and have both point to 192.168.1.1
    Here’s what the code looks like (I apologize if the line breaks are confusing, but I think it reads easily enough):

PARAM($domain)

# get the local host name
$localhost = $env:computername

# create the dns zone
dnscmd $localhost /zoneadd $domain /primary /file "$domain.dns"
   
# update Start of Authority (note: need to use single quotes around @ or will error out)
dnscmd $localhost /recordadd $domain ‘@’ SOA ns1.sweeneyops.lab me.sweeneyops.lab 1\ 3600 600 86400 3600

# add authoritative name servers
dnscmd $localhost /recordadd $domain ‘@’ NS ns1.sweeneyops.lab
dnscmd $localhost /recordadd $domain ‘@’ NS ns2.sweeneyops.lab
   
# Remove the host name from the name servers list
dnscmd $localhost /recorddelete $domain ‘@’ NS $localhost /f

# make sure zone transfers are allowed, but only for servers in the name server tab, and configure to notify
dnscmd $localhost /zoneresetsecondaries $domain /securens /notify

# create default records for cooperindustries.com
dnscmd $localhost /recordadd $domain ‘@’ A 192.168.1.1
dnscmd $localhost /recordadd $domain www A 192.168.1.1

Now one thing worth noting here is the entry for creating the Start of Authority record (SOA).  I said I wanted to use the windows defaults for setting up the SOA, but the DNSCMD syntax requires that you provide the values for the various SOA components.  In our script, that looks like this:

dnscmd $localhost /recordadd $domain ‘@’ SOA ns1.sweeneyops.lab me.sweeneyops.lab 1\ 3600 600 86400 3600

If we were to manually enter this on Server1 to create a zone called sweeneyops.com then it would look like this:

dnscmd Server1 /recordadd sweeneyops.com ‘@’ SOA ns1.sweeneyops.lab me.sweeneyops.lab 1\ 3600 600 86400 3600

So what’s up with that bit at the end where all the numbers start?  Well, if you pull up NSLOOKUP and set your query type to SOA then look up a zone after creating it through the DNS GUI you will see the following returned in the entry:

refresh = 3600 (1 hour)
retry   = 600 (10 mins)
expire  = 86400 (1 day)
default TTL = 3600 (1 hour)

These are the default Refresh, Retry, Expire, and MinTTL values defined by Windows, which I said I wanted to use.  That’s what the “3600 600 86400 3600” part is about.  The “1\” is a serial number value required in the DNS syntax. Normally, when using the GUI, Windows would define this value automatically.  We still want it to do so, and I found that using the “1\” causes it to do just that.  Trial an error for me, benefit for you.  I haven’t tried defining a specify serial number, but I suppose you could do that and possibly pass it in as another parameter.  For now, however, lets move on.  

I run this script on Server1 (ns1.sweeneyops.lab), pass in my domain name, and it creates my primary zone.  But what about my secondary zones on Server2 (ns2.sweeneyops.lab)?  For those, I will need a slightly different script.  Our new script is going to need the IP address of ns1.sweeneyops.lab, so for the sake of argument we’ll say that it’s 192.168.1.2.  Our code is much simpler.  It takes the domain name and the IP address of the first name server. It then uses that information to create the secondary zone.  All of the records were defined on the primary server, so there’s really not much left to do.  The code, which would be run on Server2, looks like this:

PARAM([string]$domain, [string]$IP)

# get the local host name
$localhost = $env:computername

dnscmd $localhost /zoneadd $domain /Secondary $IP /file "$domain.dns"

I could probably shorten it down to a single line, but I felt that it read more easily this way.  Feel free to modify it however you see fit.  So, there you have it. Simplified scripting of DNS through PowerShell. Enjoy!

Configuring PowerShell to run as a Scheduled Task in Server 2008 R2

When configuring a PowerShell script to run as a scheduled task in Windows Server 2008 R2, there are a few things that you need to pay special attention to if you want to make sure that it actually runs.  Specifically, each Action in the Actions section of the Scheduled task should be configured as follows:

Action:

Start a Program

Program/Script:

C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe

Add arguments (optional):

-noninteractive –nologo c:\scriptpath\script.ps1

Start in (optional):

c:\scriptpath

There are a few things to note here:

  1. Do not place the script itself in the program/script filed.  Instead, you are making a call to powershell itself and then passing in the script name as a parameter. 
  2. The “-noninteractive” switch tells PowerShell that it should not present an actual shell. 
  3. The “-NoLogo” switch starts PowerShell from trying to display the copyright.  Frankly, I’m not sure this is critical, but I’ve always done it.
  4. Try and avoid placing your script in a path that contains spaces.  This means you don’t have to screw around with quotations.  This is not so much a road-block as a hurdle, but I personally like to keep things as simple as possible and I find this helps.
  5. If your script reads from or outputs to a file then make sure you don’t forget the Start In option. I like to specify this location as being the same location as my script file.  The reason I do this is that it allows me to write a script and simply specify the name of the file to read from or write to without having to specify the path in the script.  This is a matter of preference, but I find that it helps me keep things simple.

Delegating Permissions to Group Policy Objects using PowerShell

Today I was asked to delegate permissions to a very large set of group policy objects.  My first thought was “ugh!” as I envisioned going though each and everyone in the Group Policy Management Console (GPMC).  A moment later my outlook changed when I realized I could just use PowerShell and make the changes in short order.  Here’s how:

First, I need to load the Active Directory and Group Policy modules in PowerShell.  If you don’t have access to these modules then you need to install the Remote Server Administration Tools (RSAT) for Windows 7 w/ Service Pack 1.  The RSAT tools are built into Windows Server 2008 R2 and just need to be activated .  On Windows 7, once installed, you can activate the Active Directory Module as a unique Windows Feature.  The Group Policy Module will be activated when you activate the Group Policy Management Tools feature, which include the GPMC.  Once you’re past that, loading them is a breeze:

import-module activedirectory

import-module grouppolicy

Second, I need to get the list of Group Policy Objects in question.  I know I have a large number of GPO’s, but I’m only interested in a subset.  Fortunately, I know that all the GPO’s I’m interested in begin with “SweeneyOps” so I’ll adjust my query to return only GPO’s with that prefix.  We get the list of GPO’s using the Get-GPO cmdlet, and will dump the resulting set into a variable called $GPOList.

$GPOList = get-gpo –all | where{$_.displayname –like “SweeneyOps*”}

Now that I have all my GPO’s I can iterate through that list to grant permissions to a specific security group, but first in need the group.  For the sake of argument, we’ll call this group “GPO-Admins.”  The Group Policy cmdlet that we’re going to use actually requires that we provide either the domain-qualified name of the security principal (domain\account), or the sAMAccountName of the user, group or computer that we will be granting permissions.  In this case we are granting permissions to a group, and I find it easiest to use the sAMAccountName, so I’ll grab that and place it in a variable called $group.

$group = $(get-adgroup “GPO-Admins”).sAMAccountName

Ok, group in tow, we can proceed to the delegation.  To do this, we’re going to use the Set-GPPermissions cmdlet. In this case I just happen to want to grant (nearly) full access to the group, so I’ll use GpoEditDeleteModifySecurity as the value for the PermissionLevel parameter.

$gpolist | foreach{set-gppermissions -guid $_.id -targetname $group -targettype Group -PermissionLevel GpoEditDeleteModifySecurity}

And we’re done!  This process will differ slightly in each environment of course. You may need to specify a different domain, or server, or you might need to allocate permissions to multiple groups, etc…  The fundamental principal remains the same, however.  Get your GPO(s), get your group(s), select the level of access, and then delegate access using the Set-GPPermissions cmdlet. 

Active Directory Timestamp Conversion through PowerShell

At this point I’ve become a complete convert to Active Directory administration through PowerShell 2.0 and the Active Directory Module.  Granted, there are some tasks which just make more sense to execute through the GUI, but most of the time I find that PowerShell is a superior interface. Here’s a link for anyone who isn’t familiar with it:

http://technet.microsoft.com/en-us/library/ee617195.aspx

There was one thing that really used to bother me about it, though, and that was the timestamps.  Let’s say I do a lookup on my account:

PS C:\tools\powershell> $user = get-aduser bsweeney -properties lastlogontimestamp

PS C:\tools\powershell> $user

DistinguishedName : CN=bsweeney,OU=testusers,DC=sweeneyops,DC=lab
Enabled : True
GivenName : bsweeney
lastlogontimestamp : 129833580060255031
Name : bsweeney
ObjectClass : user
ObjectGUID  : 7fbf1e01-6a59-42d6-9321-787e145dae06
SamAccountName : bsweeney
SID : S-1-5-21-374116659-2851274-313409940-46522
Surname            :
UserPrincipalName  : bsweeney@sweeneyops.lab

As you can see, the lastlogontimestamp is unintelligible.  There’s no point in outing it to a spreadsheet or trying to determine something from it.  The only option is to convert it. 

As it turns out, its actually very easy to convert.  I just didn’t know how to at the time.  Now I do, and I’m sharing with you, cuz that’s just the kind of guy I am.  The trick is to use the FromFileTime method of then datetime .NET object , which you can see in this block of code:

[datetime]::FromFileTime($mydatetime)

Where $mydatetime = $user.lastlogontimestamp

So expanding on the sample above:

PS C:\tools\powershell> $mydatetime = $user.lastlogontimestamp
PS C:\tools\powershell> $time = [datetime]::FromFileTime($mydatetime)
PS C:\tools\powershell> $time

Tuesday, June 05, 2012 4:20:06 AM

And, of course, you could collapse all that down into a single line of code:

PS C:\tools\powershell> get-aduser bsweeney -properties lastlogontimestamp | [datetime]::FromFileTime($($_.lastlogontimestamp))

Tuesday, June 05, 2012 4:20:06 AM

Pretty cool, eh?! And at this point we can now convert any timestamp in AD to a human readable format in our scripts.

Useful techniques for enumerating mailboxes in Exchange 2003 using PowerShell

I stumbled across this old post by Jonathan Medd and thought I should make a note of it: Exchange 2003, WMI and Powershell – Part 1 (Get Mailbox Info)

Basically it’s a powershell command that will enumerate all the data in the Exchange stores of a 2003 environment.  I’m not sure if it works in 2007 and up, but I do still deal with 2003 and it gave me a few ideas which I thought I would share here:

get-mailbox.ps1

# extracts the details of a specific user’s mailbox from the target server.

# takes the servername and the displayname of the user as parameters.

PARAM([string]$server,[string]$name)
Get-WMIObject -namespace root\MicrosoftExchangeV2 -class Exchange_Mailbox -computer server | ?{$_.MailboxDisplayName -like "*$name*"} | sort-object MailboxDisplayName | format-table MailboxDisplayName,Servername,StorageGroupName,StoreName,Size

 

report-mailstore.ps1

# generates a report about every mailbox on an exchange server in tab-separated format for easy import into excel.

# takes the servername as a parameter

PARAM([string]$server)

$logfile = "$server.report.txt"

if(test-path $logfile){ri $logfile}

add-content $logfile "MailboxDisplayName`tServername`tStorageGroupName`tStoreName`tSize"

$mailboxes = Get-WMIObject -namespace root\MicrosoftExchangeV2 -class Exchange_Mailbox -computer $server | sort-object MailboxDisplayName
foreach($mailbox in $mailboxes)
{
    $name = $mailbox.MailboxDisplayName
    $mailsrv = $mailbox.Servername
    $sg = $mailbox.StorageGroupName
    $store = $mailbox.StoreName
    $size = $mailbox.Size
   
    add-content $logfile "$name`t$mailsrv`t$sg`t$store`t$size"
}

Check availability of Remote Desktop Services or XenApp with PowerShell

Here’s a way to check port availability with PowerShell.

$server = “server1”

$port = 3389

$(new-object Net.Sockets.TcpClient).Connect($server, $port)

This initiates a connection to $server on port number $port.  If the connection is successful then nothing happens.  If the connection fails then an error exception is generated of the type ‘System.Net.Sockets.SocketException’. So this has a lot of valuable applications in and of itself, but where I found it particularly useful was in testing the availability of RDP/ICA after rebooting a Remote Desktop Services server or XenApp server.

To do this, I use the following do/until loop:

$server = “Server1”

do
{
    try
    {
        # Test ICA on ports 2598 and 1494
        $(new-object Net.Sockets.TcpClient).Connect($server, $port1)
        $(new-object Net.Sockets.TcpClient).Connect($server, $port2)
           
        # Test RDP on port 3389
        $(new-object Net.Sockets.TcpClient).Connect($server, $port3)
       
        # Sets $isup to true by default
        $isup = $true
    }
    catch [System.Net.Sockets.SocketException]
    {
        $isup = $false
    }
}
until($isup)

So the script tests on ports 2598, 1494, and 2278 and sets the $isup variable to $false by default.  If everything connects then the loop ends and the script continues, but if an error exception is generated by any of the three ports then a socket error is detected and the loop continues.  As soon as all three ports are responding then you should be able to connect using RDP/ICA.

I love PowerShell.