Archive

Archive for the ‘Scripting’ Category

Search Your Compellent Storage Center Using Windows PowerShell

March 3rd, 2010 Justin Braun No comments

Using Windows PowerShell and the Compellent Storage Center Command Set for PowerShell, I have created a simple script that performs searches for server and volume objects providing results of each in addition to their related mappings.  This can be helpful if you have multiple levels of server or volume folders.

# Name:       Storage Center Search
# Usage:      Search for volume or server objects across a Storage Center
# Author:     Justin Braun
# Date:              February 17, 2010

# This sample script is provided "as is" with no warranty whatsoever; 
# you assume all responsibilty of the consequences of use.

$scname = Read-Host "What Storage Center do you want to search? (IP or hostname)"
$scusername = Read-Host "What is the username for your account on this Storage Center?"
$scpassword = Read-Host -AsSecureString -Prompt "What is the password?"
$findsource = Read-Host "What type of object are you looking for? (S=server, V=volume)"
$searchterm = Read-Host "Enter the text you want to search for (wildcards OK)"

if($findsource -ne "S" -and $findsource -ne "V")
{
       # Invalid parameter entered.
       Write-Host "`nInvalid search source specified.`n" -ForegroundColor Red
       Break
}

# Valid parameter found - start script

# Instantiate connection
Write-Host "`nOpening connection...";

try
{
       $conn = Get-SCConnection -HostName $scname -User $scusername -Password $scpassword;
}
catch
{
       Write-Host "Error: " $_;
       Break;
}

Write-Host "Connection established to $scname...";

# What are we searching for?
switch ($findsource)
{
       "S"
       {
              # Find a server
              $servers = Get-SCServer -Connection $conn | where { $_.Name -like $searchterm }

              if($servers -ne $null)
              {
                     # Found some objects that might match
                     Write-Host "`nThe following servers might match your query:"
                     $servers | ft -AutoSize Name, LogicalPath, WorldWideNames
              }
              else
              {
                     # No objects found
                     Write-Host "`nSorry, no objects were found that match your query."
              }
       }

       "V"
       {
              # Find a volume
              $vols = Get-SCVolume -Connection $conn | where { $_.Name -like $searchterm }

              if($vols -ne $null)
              {
                     # Found some objects that might match
                     Write-Host "`nThe following volumes might match your query:"

                     foreach($vol in $vols)
                     {
                           # Create an output object for table formatting
                           $obj = new-Object PSObject;

                           # Add member properties with their name and value pair
                           $obj | Add-Member NoteProperty VolumeName($vol.Name);
                           $obj | Add-Member NoteProperty LogicalPath($vol.LogicalPath);

                           #Get Mappings to volume
                           $mappings = Get-SCVolumeMap -VolumeIndex $vol.Index -Connection $conn

                           if($mappings -ne $null)
                           {
                                  $maps = ""
                                  foreach($mapping in $mappings)
                                  {
                                         $maps += $mapping.ServerName + " "
                                  }

                                  $obj | Add-Member NoteProperty Mappings($maps);
                           }
                           else
                           {
                                  # No mappings found
                                  $obj | Add-Member NoteProperty Mappings("None");
                           }

                           # Write the output to the screen
                           Write-Output $obj;
                     }
              }
              else
              {
                     # No objects found
                     Write-Host "`nSorry, no objects were found that match your query."
              }
       }
}

If you have ideas for scripts or would like to share your creations with other Compellent customers, check out the Forums at the Compellent Customer Portal. [authentication required]

Categories: Compellent, PowerShell, Scripting Tags:

Compellent Volume Reporting with PowerShell

December 2nd, 2009 Justin Braun No comments

Compellent Enterprise Manager works great for managing your Storage Center environment and providing reports on volume usage and utilization.

I was looking for a little different spin on the information.  I was looking for a cumulative volume count across an entire Storage Center, plus a total count of replays on the system, and how many of the volumes that exist are actually mapped up to a server object.

For example, the test system that I ran my script on determined that we had over 900 volumes with over 3,000 replays.  We also realized that we had some cleanup to do when we figured out that only 180 of the volumes were actually mapped up.

I did build into the script to collect the page count of each replay so you could tell how large they were if you wanted to; just the calculation needs to be added.

# NAME: VolumeInfo.ps1
# DESC: PowerShell script to report on volume information
# BY  : Justin Braun, Compellent Technologies, Inc.
# DATE: December 1, 2009
# VER : 1.0
#
# THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND.  THE ENTIRE
# RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER.
#
# NOTE: This script assumes a default Get-SCConnection already exists.
 
# Collection
$colVolumes = @()
 
foreach($volume in Get-SCVolume)
{
    Write-Host "Gathering volume data for $volume.Name..."
    
    $replays = $null
    $mappings = $null
    $pagecount = 0
    
    # Volume Information
    $ReportData = New-Object System.Object
    $ReportData | Add-Member -Type NoteProperty -Name "Volume Index" -Value $volume.Index
    $ReportData | Add-Member -Type NoteProperty -Name "Volume Name" -Value $volume.Name
    $ReportData | Add-Member -Type NoteProperty -Name "Volume Size" -Value $volume.Size
    $ReportData | Add-Member -Type NoteProperty -Name "Block Count" -Value $volume.BlockCount
    $ReportData | Add-Member -Type NoteProperty -Name "Created By" -Value $volume.CreateUser
    $ReportData | Add-Member -Type NoteProperty -Name "Created On" -Value $volume.CreateTime
    $ReportData | Add-Member -Type NoteProperty -Name "Modified By" -Value $volume.ModifyUser
    $ReportData | Add-Member -Type NoteProperty -Name "Modified On" -Value $volume.ModifyTime
    $ReportData | Add-Member -Type NoteProperty -Name "Folder" -Value $volume.ParentFolder
    
    # Replay Count Information
    Write-Host "Gathering replay information for $volume.Name..."
    $replays = Get-SCReplay -SourceVolumeIndex $volume.Index
    $ReportData | Add-Member -Type NoteProperty -Name "Replay Count" -Value $replays.Count
    
    # Replay Cumulative Page Information
    Write-Host "Gathering replay page count information for $volume.Name..."
    foreach($replay in $replays)
    {
        $pagecount += $replay.OwnedPageCount
    }
    
    $ReportData | Add-Member -Type NoteProperty -Name "Total Replay Pages" -Value $pagecount
    
    # Volume Mapping Information
    Write-Host "Gathering volume mapping information for $volume.Name..."
    $mappings = Get-SCVolumeMap -VolumeIndex $volume.Index
    
    if($mappings -eq $null)
    {
        $ReportData | Add-Member -Type NoteProperty -Name "Mappings" -Value "No"
    }
    else
    {
        $ReportData | Add-Member -Type NoteProperty -Name "Mappings" -Value "Yes"
    }
    
    # Add to collection
    $colVolumes += $ReportData
    
}
 
# Outfile ReportData Contents
Write-Host "Writing output file..."
 
$colVolumes | export-csv -path "c:\volumeinfo.txt"
 
Write-Host "Done!"

If you have any ideas on how this script could be more useful in your environment, drop me a comment below.

Compellent Disk Management with PowerShell: Windows Server 2008 Disks

July 29th, 2009 Justin Braun No comments

I was provisioning some Compellent storage today for a a series of tests that I am working on that required 62 volumes per server on two different servers.  These volumes are multi-pathed and although using the Compellent Storage Center GUI is easy and straightforward, completing this process would take a long time doing by hand and seemed fit to be automated using the Compellent Storage Center Command Set for Windows PowerShell.

I wrote a script a while back that handles my provisioning for me; in this case a couple of mount point root volumes followed by data volumes that would be accessed by mount point instead of drive letter.  The script is flexible enough to handle different volume counts and whether or not drive letters would be used, but the catch was I had only used it with Windows Server 2003. 

I tried to run the script this morning and found a flaw pretty quickly.  The volume was created on the Storage Center, mapped properly across the available paths, but when the script tried to initialize the volume in Windows, it would come back as “failed to initialize” with VDS error code 80070013.  This VDS error code indicates that the “media is write-protected”.  How could that be on a new volume?

Windows 2008 changed the way disk management is handled especially around delivery of the disk to the server.  By default, a disk mapped to a Windows 2008 server via VDS will be delivered in offline mode and also read-only.  In Windows Server 2008 there is a policy new to Windows related to SAN disks. This "SAN policy" determines whether a newly discovered disk is brought online or remains offline, and whether it is made read/write or remains read-only.  By default, the “Offline All” policy is set.  This means All newly discovered disks remain offline and read-only.  You can change this default policy in DISKPART by running the SAN POLICY=<POLICY NAME> from a DISKPART command prompt.

image

changing the default SAN policy in DISKPART

 

You can read more here, but in the meantime, the fix for this from a scripting perspective is quite simple.  The inability to initialize the disk because it was read-only was due to the SAN policy which presented the volume in a read-only fashion (and offline too).  We can change the disk attribute  of the volume so it is not read-only and then we can bring the disk online so it is usable.  Here is a sample of how to use the Command Set to change the read-only attribute and the state of the drive:

Write-Output "Bringing Disk Online..."
Set-DiskDevice -SerialNumber $scvolume.SerialNumber -Online
Set-DiskDevice -SerialNumber $scvolume.SerialNumber -ReadOnly:$false

$scvolume is a variable that refers to the volume object that is created when we create a new volume using New-SCVolume.  The serial number is used to identify the disk mapped to the Windows Server.  It is also important to note that although the “Online” and “ReadOnly” switches come from the same cmdlet, these must be executed separately as they are in the sample. (Thanks for that important tidbit, Sean!)

Categories: Compellent, PowerShell, Scripting, Storage Tags:

Checking Partition Alignment with PowerShell

July 21st, 2009 Justin Braun No comments

Here is an easy way to determine the partition alignment of any given disk on the local system or remotely.

$OffsetKB = @{label=”Offset(KB)”;Expression={$_.StartingOffset/1024 -as [int]}}
$SizeMB = @{label=”Size(MB)”;Expression={$_.Size/1MB -as [int]}} 
 
Get-WmiObject -ComputerName "localhost" -Class "Win32_DiskPartition" | ft`
SystemName, Name, DiskIndex, $SizeMB, $OffsetKB  -AutoSize

This will output table that looks like this:

image

Why should you care about this?  This is particularly useful for determining partition alignment of existing disks that may be running applications like Exchange or SQL.  Exchange recommends a 64K partition alignment as does SQL in most cases.  In Windows Server 2008, partition alignment is automatic and defaults to 1024KB for new partitions. Note that the alignment of partitions on servers that were upgraded from Windows 2003 to 2008 are not changed.

Categories: PowerShell, Scripting, Windows Tags:

Discovering Stale User Accounts with PowerShell

March 30th, 2009 Justin Braun No comments

Back in December I wrote a posting on “Discovering Stale Computer Accounts with PowerShell”.  Today I received an email from one of my readers (Thanks Matt for writing!) trying to adjust the script to query for stale user accounts.  The script that I created in the previous entry is a good starting point, but requires some modifications to work properly for user accounts.

The biggest change in the script is that the DirectorySearcher filter has to be modified to look for user accounts.  Simply changing  the filter to “user” from “computer” doesn’t quite work as for some reason ADSI will retrieve both the user accounts as well as the computer accounts.  We can further limit the search by adding the ObjectCategory in addition to the ObjectClass.

This particular example will query on the last password change date.

function Get-StaleUserAccounts
{   
    # Use Directory Services object to attach to the domain
    $searcher = new-object DirectoryServices.DirectorySearcher([ADSI]"")
    
    # Filter down to user accounts
    #when you query for objectClass=User, you will not only get user accounts but also computer accounts. 
    #To limit the search to true user accounts, you would have to also include the objectCategory
    $searcher.filter = "(&(objectCategory=person)(objectClass=User))"
    
    # Cache the results
    $searcher.CacheResults = $true
    $searcher.SearchScope = “Subtree”
    $searcher.PageSize = 1000
    
    # Find anything you can that matches the definition of being a user object
    $accounts = $searcher.FindAll()
    
    # Check to make sure we found some accounts
    if($accounts.Count -gt 0)
    {             
        foreach($account in $accounts)
        {
            $LastPassChange = [datetime]::FromFileTimeUTC($account.Properties["pwdlastset"][0]);    
        
            # Determine the timespan between the two dates
            $datediff = new-TimeSpan $LastPassChange $(Get-Date);
            
            # Create an output object for table formatting
            $obj = new-Object PSObject;
            
            # Add member properties with their name and value pair
            $obj | Add-Member NoteProperty AccountName($account.Properties["name"][0]);
            $obj | Add-Member NoteProperty LastPasswordChange($LastPassChange);
            $obj | Add-Member NoteProperty DaysSinceChange($datediff.Days);
            
            # Write the output to the screen
            Write-Output $obj;
        }
    }
}
 
# Get user accounts where a password change hasn't occurred in 60 days or more
# If nothing outputted, then there are no accounts that meet that criteria
Get-StaleUserAccounts |Where-Object {$_.DaysSinceChange -gt 60}

Categories: Development, PowerShell, Scripting Tags:

The Case of the Missing Interview

January 22nd, 2009 Justin Braun No comments

Even though we had a number of technical difficulties, I really enjoyed the interview with Jon and Hal over at the Powerscripting Podcast.  I was excited to listen to the playback to the interview which should’ve been available a few days after we recorded the live show, but the interview went missing from what I hear, so Episode 54 was MIA.  Our people are working on seeing if we can reschedule the interview for another show to take another run at it.

Categories: Compellent, PowerShell, Scripting Tags:

Powerscripting Podcast

January 7th, 2009 Justin Braun No comments

I will be the guest for an interview on the Powerscripting Podcast, a Powershell podcast designed to help people learn Windows PowerShell.

I will be on to discuss the Compellent Command Set for Windows PowerShell, our free offering for customers who want the ability to manage their Compellent Storage Center via script, or automate administration processes, a lot of which I will talk about in the interview.

This is a live show and the hosts will take questions during the broadcast, So, tune in Thursday night (January 8) at 9 AM EST (8 PM CST)!

Categories: Compellent, PowerShell, Scripting Tags:

Finding Active IP Addresses and Resolved Hostnames with PowerShell

December 17th, 2008 Justin Braun 1 comment

Ever wanted to know what IP addresses are being used on a particular subnet and what each IP address resolves to which hostname, if any?  This script accomplishes just that.

# Continue to run script when an error occurs
$ErrorActionPreference = "SilentlyContinue"
 
# Process through the loop of IP addresses 1-254
1..254 | foreach -Process `
{
    # We're combining the contents of different objects, so create our own
    $obj = New-Object PSObject;
    
    # Perform the Ping using WMI
    $ping = get-WmiObject -Class Win32_PingStatus -Filter ("Address='10.10.2." + $_ + "'");
    
    # Put the IP Address into our object
    $obj | Add-Member NoteProperty IPAddress($ping.Address);
    
    # Take the Ping Status Code that is returned (0 = pingable)
    $obj | Add-Member NoteProperty StatusCode($ping.StatusCode);
      
    # If we can ping the address, let's try to resolve the hostname
    if($ping.StatusCode -eq 0)
    {
          # Record that the ping was successful
           $obj | Add-Member NoteProperty Status("Online");
        
        # Try to resolve the IP address to a hostname in DNS
        $dns = [System.Net.Dns]::GetHostByAddress($ping.Address);
        
           if($dns -ne $null)
           {
            # Add the resolved hostname to our collection
             $obj | Add-Member NoteProperty ResolvedHostName($dns.HostName);
           }
           else
           {
            # Couldn't resolve the IP address to a hostname
               $obj | Add-Member NoteProperty ResolvedHostName("");
           }
    }
    else
    {
        # Can't ping IP address, so mark host as offline
           $obj | Add-Member NoteProperty ResolvedHostName("");
           $obj | Add-Member NoteProperty Status("Offline");
    }
      
    # Write the collection out
    Write-Output $obj;
    
    # Cleanup DNS object
    $dns = $null;
}
Categories: PowerShell, Scripting Tags:

Discovering Stale Computer Accounts with PowerShell

December 15th, 2008 Justin Braun 1 comment

We all know that most test domains are the perfect breeding ground for non-standard practices.  This includes the lack of managing user accounts, computer accounts, DNS records and the like.

ADSI scripting has always been challenging, but I did do some of it back in my IT days when writing code in VBScript.  I wanted to try to tie in to a test domain to determine what computer accounts hadn’t had their password changed in the last 90 days.  By default, Windows machines will talk to the domain in which they have a computer account in and change their password every 30 days.  With that in mind, we can look for computer accounts who haven’t changed their computer account password in the last month.

We probably want to allow a little more than 30 days; perhaps computer accounts might be considered stale after 60 or 90 days.  That means that the computer hasn’t made network contact in the allotted amount of time.

Within PowerShell I tapped into the ADSI adapter and some of the .NET Framework objects that let me search Active Directory.  Really I was only interested in the computer name and the date of the last password change.  Both of those were easy to access.  Here is a copy of what my script looks like:

function Get-DomainComputerAccounts
{   
    # Use Directory Services object to attach to the domain
    $searcher = new-object DirectoryServices.DirectorySearcher([ADSI]"")
    
    # Filter down to computer accounts
    $searcher.filter = "(&(objectClass=computer))"
    
    # Cache the results
    $searcher.CacheResults = $true
    $searcher.SearchScope = “Subtree”
    $searcher.PageSize = 1000
    
    # Find anything you can that matches the definition of being a computer object
    $accounts = $searcher.FindAll()
    
    # Check to make sure we found some accounts
    if($accounts.Count -gt 0)
    {             
        foreach($account in $accounts)
        {
            # Property that contains the last password change in long integer format
            $pwdlastset = $account.Properties["pwdlastset"];
            
            # Convert the long integer to normal DateTime format
            $lastchange = [datetime]::FromFileTimeUTC($pwdlastset[0]);
            
            # Determine the timespan between the two dates
            $datediff = new-TimeSpan $lastchange $(Get-Date);
        
            # Create an output object for table formatting
            $obj = new-Object PSObject;
            
            # Add member properties with their name and value pair
            $obj | Add-Member NoteProperty ComputerName($account.Properties["name"][0]);
            $obj | Add-Member NoteProperty LastPasswordChange($lastchange);
            $obj | Add-Member NoteProperty DaysSinceChange($datediff.Days);
            
            # Write the output to the screen
            Write-Output $obj;
        }
    }
}
 
# Get computer accounts where a password change hasn't occurred in 60 days or more
# If nothing outputted, then there are no accounts that meet that criteria
Get-DomainComputerAccounts |Where-Object {$_.DaysSinceChange -gt 60}

This script could be modified to include functionality that would take the suspect computer accounts and disable them, then eventually delete them.

Categories: PowerShell, Scripting Tags:

Using Powershell To Generate Test Mailboxes

August 2nd, 2008 Justin Braun No comments

There are situations where you may want to generate a number of test mailboxes whether it be for a demo or another scenario.  With Exchange 2007, you can leverage Powershell cmdlets to complete this process for you in just seconds.

First, I start out with the a CSV file that contains a limited number of columns, basically the minimum needed to create an Active Directory user account and mailbox-enable it.  Since I have a passion for baseball, my test CSV file is a tribute to some of my favorite players in history.  This is what the sample users.csv file looks like:

UPN,FirstName,LastName,Password
bruth,Babe,Ruth,TestP@ssword1!
mmantle,Mickey,Mantle,TestP@ssword1!
hwagner,Honus,Wagner,TestP@ssword1!
brobinson,Brooks,Robinson,TestP@ssword1!
yberra,Yogi,Berra,TestP@ssword1!
kpuckett,Kirby,Puckett,TestP@ssword1!
rcarew,Rod,Carew,TestP@ssword1!
hkillebrew,Harmon,Killebrew,TestP@ssword1!
twilliams,Ted,Williams,TestP@ssword1!
jdimaggio,Joe,DiMaggio,TestP@ssword1!

Next, it only takes a couple lines of code for us to generate the accounts and mailboxes.  You can take this code and save it into a file named something like createmailboxes.ps1.  This is what the script should look like:

# Test Mailbox Creation Script

# Variables Used Globally
$database = “First Storage GroupMailbox Database”
$ou = “Users”
$upnsuffix = “@e2k7test.local”

import-csv users.csv | foreach {$pass = ConvertTo-SecureString $_.Password -AsPlainText -Force; New-Mailbox -Name ($_.FirstName+” “+$_.LastName) -Password $pass -UserPrincipalName ($_.UPN+$upnsuffix) -Database $database -OrganizationalUnit $ou -FirstName $_.FirstName -LastName $_.LastName -DisplayName ($_.FirstName+” “+$_.LastName)}

In my script I include some variables that I reuse for all of my users.  In this case, they are all going to be part of the same organizational unit (OU) in Active Directory and their mailboxes will all be part of the same mailbox database.  They are also part of the same domain so the suffix for their User Principal Name (UPN) will be the same.

Literally, the meat of the script is just one line of code thanks for the piping you can do in Powershell.

The import-csv cmdlet takes in a comma-separated text file and lets us read its contents like a data object.  Basically we’re taking the contents of the CSV file and passing it into the next segment of code which is the “For Each” block.

Since we want to create an account and mailbox for each line in the CSV file, we’ll do that line by line processing within the “For Each” block.  The first command within that block is where we’ll save the password as a variable.  Since the password can’t be passed into the cmdlet as plain text, we have to convert it to a secure string using the ConvertTo-SecureString cmdlet.  We pass in the plain text password and it returns a secure string that can be used.

Next we call the New-Mailbox cmdlet which will create the account and mailbox enable it.  We’ll pass in information like the name of the account, first name, last name, display name, UPN, mailbox database, OU, and password.  There are additional fields that you can include, but are not required.

We can access the different “fields” from the CSV by calling them with the $_.ColumnName format as you can see in the script sample. 

This clearly simplifies the process of bulk account creation and provisioning for Exchange.  We’ll work on continuing to evolve this into a more complex script that adds some additional functionality.

Categories: Exchange, PowerShell, Scripting Tags: