Wednesday, December 2, 2009 10:49 PM Central Time
Posted by Justin
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.
Wednesday, July 29, 2009 2:21 PM Central Time
Posted by Justin
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.

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!)
Tuesday, July 21, 2009 9:07 AM Central Time
Posted by Justin
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:
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.
Monday, March 30, 2009 10:03 PM Central Time
Posted by Justin
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}