Archive

Archive for the ‘PowerShell’ Category

Is Your PowerShell Slow to Start?

April 2nd, 2010 Justin Braun 2 comments

I ran into a scenario last week where we had loaded the Compellent Storage Center Command Set for Windows PowerShell on a server.  When we launched the shell shortcut, the window opened but took a long time to get to a PowerShell command prompt.

So, what causes slow start-up when loading PowerShell?

The most common reason seems to be that machines experiencing this slowness are not connected to the Internet.

What?

Well, when Since PowerShell is loading the Compellent Command Set DLL externally, .NET has a security feature to check Microsoft’s CRL, or Certificate Relocation List.  This process verifies the authenticity and validity of the software publisher’s certificate.  If this check can’t reach the Internet, the process will time out after several minutes.  Now, this doesn’t prevent anything from loading (which seems odd), but it takes a couple minutes for a process that should take only a couple of seconds.

The easiest resolution at this point appears to allow Internet access to the server.  If that is not possible, you can disable the check for the publisher’s certificate revocation.  You can do this from Internet Explorer (or Control Panel, Internet Options) by clicking on Tools, Internet Options.  Under the Security section of the Advanced tab, uncheck “Check for publisher’s certificate revocation”.

image

NOTE: These type of security features are in place for a reason.  Take extreme caution when considering disabling these.

Categories: Development, PowerShell, Windows Tags:

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:

Exchange 2010: Database Copies with PowerShell

December 9th, 2009 Justin Braun No comments

I needed a quick and dirty method to add database copies to other servers in an Exchange 2010 Database Availability Group (DAG).  I had three servers, each with 10 databases.  Each one of those databases should have a copy on the other two servers. 

Now, this is painful if you have to use the GUI to do it – not that it takes a lot, but it’s time consuming.  The Add-MailboxDatabaseCopy cmdlet is very helpful in PowerShell, but still I wanted to automate it since my naming conventions were pretty standard.

So, I wrote a PowerShell script to automate this process.  Essentially, the script has a few variables and I dome some loop magic to drop everything into place. 

$servers = "E2K10MBX01", "E2K10MBX02", "E2K10MBX03"
$databases = "DB1", "DB2", "DB3", "DB4", "DB5", "DB6", "DB7", "DB8", "DB9", "DB10"
 
foreach($database in $databases)
{
       foreach($server in $servers)
       {
              foreach($copyserver in $servers)
              {
                     "Adding mailbox copy for $server-$database to $copyserver..."
                     Add-MailboxDatabaseCopy -Identity $server-$database -MailboxServer $copyserver -ErrorAction "Continue"
              }
       }
}

This script is quick and dirty for a lab environment.  Keep in mind that this doesn’t do any error checking and there is no validation to see if a database is already homed to a server.  This just uses the cmdlet to try to create the database copy; if it is successful, you’re good to go.  If it fails, the script just keeps on rolling.  No harm, no foul.

Categories: Exchange, PowerShell 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.

PowerShell with Compellent and Exchange 2010

December 2nd, 2009 Justin Braun No comments

I’ve been doing lots of work in the lab lately with Exchange 2010 to understand all the new changes and how it works with the Compellent Storage Center.

With Exchange 2010, the concept of Storage Groups no longer exists.  Databases are the sole object and are a peer to the server now.  Database names must be unique, but can be moved from server to server as necessary.

In the past, I’ve shared some scripts on how to provision storage for an Exchange 2007 environment.  I’ve slightly reworked this script to account for no longer needing storage groups, but to also automatically create the mailbox database on the Exchange Server and mount it when completed.

# NAME: Exchange2010LabCreate.ps1
# DESC: PowerShell script to create and map volumes for Exchange 2010 Lab
# BY  : Justin Braun, Compellent Technologies, Inc.
# DATE: November 24, 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.
#
 
####################################################
# ERROR HANDLING
####################################################
    #"SilentlyContinue": do not print, continue 
    #"Continue": Print, continue (this is the default) 
    #"Stop": Halt the command or script 
    #"Inquire": Ask the user what to do 
    $ErrorActionPreference = "Inquire"
 
####################################################
# STORAGE CENTER CONFIGURATION INFORMATION
####################################################
    $schost = "storagecenter.lab.test"
    $user = "username"
    $pass = "password"
 
####################################################
# EXCHANGE STORAGE CONFIGURATION
####################################################    
    # Number of Databases Per Server
    $dbsize = "1TB"
    $dbtotal = 1
    $dbDiskFolder = "Assigned"
 
####################################################
# SERVER INFORMATION
####################################################
    # Server to Map To (server defintion on CSC must match server name in Windows because of VDS)
    $ServerName = "E2K10MBX01"
 
####################################################
# MISC. CONFIGURATION SETTINGS
####################################################
    # Volume folder name
    $SCParentFolderName = "E2K10MBX01"
    
    # Use custom disk folders for each volume? (if $false, then a single disk folder config is assumed)
    $useCustomDiskFolders = $false
    
    #Mountpoints (set to $true if mountpoint volumes weren't previously created.  Mountpoint volumes will be 1G by default)
    $createMountpointRoot = $true
    
    # Mountpoint Root
    $dbmproot = "M:\Exchange"
    $dbRootDrive = "M:"
 
##########################################################################################
##########################################################################################
# DO NOT EDIT BELOW THIS LINE !
##########################################################################################
##########################################################################################
 
 
# Creates volume with specified name and size using connection instantiated on script launch
function CreateVolume
{
    param
    (
        [string]     $VolumeName,
        [string]     $VolumeSize,
        [string]     $MPRoot,
        [string]     $SCDiskFolder,
        [bool]         $IsMountPoint
    )
 
    Write-Output "Creating new volume: $volumename..."
    if($useCustomDiskFolders -eq $true)
    {$scvolume = New-SCVolume -Name $VolumeName -Size $VolumeSize -ParentFolder $SCParentFolderName -StorageType $SCDiskFolder;}
    else
    {$scvolume = New-SCVolume -Name $VolumeName -Size $VolumeSize -ParentFolder $SCParentFolderName;}
 
    # Maps volume previously created and returned from CreateVolume function
    Write-Output "Mapping new volume $volumename to $servername..."
    
    # Map Volume (if multiple HBA ports are server will be used, make sure that MPIO is installed on server and remove -SinglePath switch from next line
    New-SCVolumeMap -VolumeIndex $scvolume.Index -ServerIndex $scserver.Index -SinglePath
 
    # Rescan Server
    Write-Output "Rescanning server for new volume..."
    Rescan-DiskDevice -Server $ServerName -RescanDelay 5
 
    # Issue Drive Letter / Mount Point
    Write-Output "Creating access path for new volume..."
    $device = Get-DiskDevice -SerialNumber $scvolume.SerialNumber
    
    # Check to see if the device is there yet after initial rescan
    if($device -eq $null)
    {
        # Device is still null, so let's perform up to 10 rescans before we move on
        $scancount = 0
        
        do
        {
            # Rescan the disk
            Write-Output "Rescanning server for new volume..."
            Rescan-DiskDevice -Server $ServerName -RescanDelay 5
            $scancount ++
            
            # Try getting the device again
            $device = Get-DiskDevice -SerialNumber $scvolume.SerialNumber
        }
        until($device -ne $null -or $scancount -eq 10)
    }
    
    # Set variable (this is only used if this is a drive letter mount)
    $finalpath = $MPRoot
    
    # Set full mountpoint path (create path if it doesn't exist)
    if($IsMountPoint -eq $true)
    {
        $finalpath = "$MPRoot\$VolumeName"
        
        # Check to make sure the full mountpoint path acutually exists, otherwise create it
        if (!(Test-Path -path "$finalpath\"))
        {
            New-Item "$finalpath\" -type directory
        }
    }
    
    Write-Output "Onlining Disk and setting access path to $finalpath..."
 
    # Finish creation of mountpoint/drive access
    Set-DiskDevice -SerialNumber $device.SerialNumber -Online
    Set-DiskDevice -SerialNumber $device.SerialNumber -ReadOnly:$false 
    $newvol = New-Volume -DeviceName $device.DeviceName -Server $ServerName -Label $VolumeName -AccessPath $finalpath
    
    # Null out device
    $device = $null
}
 
function LoadSnapins
{
    # Load Exchange Management Shell & Compellent Storage Center Snapins (if not already)
     $LoadedSnapins = Get-PSSnapin;
    $SnapinsToLoad = "Compellent.StorageCenter.Scripting", "Microsoft.Exchange.Management.PowerShell.E2010"
    
    "Adding PowerShell Snapins..."
 
    foreach($snapin in $SnapinsToLoad)
    {
        if (get-pssnapin $snapin -ea "silentlycontinue") 
        {
            write-host "$snapin is already loaded."
        }
        elseif (get-pssnapin $snapin -registered -ea "silentlycontinue") 
        {
            Add-PSSnapin $snapin
            Write-Host "$snapin is now loaded."
        }
        else 
        {
            write-host "PSSnapin $snapin not found" -foregroundcolor Red
        }
    }
}
 
#############################
# START SCRIPT
#############################
 
$started = Get-Date
 
#Load Requested Snapins
LoadSnapins
 
# Initialize Connection for Storage Center
#$pass = Read-Host -AsSecureString -Prompt "Please provide the Storage Center password for $user"
$securepass = ConvertTo-SecureString $pass -AsPlainText -Force
$connection = Get-SCConnection -HostName $schost -User $user -Password $securepass -Save $schost -Default
 
# Create new Volume Folder if it doesn't exist
$volumefolder = Get-SCVolumeFolder -Name $SCParentFolderName
if($volumefolder -eq $null)
{
    Write-Output "Creating new volume folder: $SCParentFolderName..."
    $volumefolder = New-SCVolumeFolder -Name $SCParentFolderName;
}
 
# Get server information for the server that we are mapping all of the volumes to
$scserver = Get-SCServer -Name $ServerName
 
# Create New Mount Point Volumes for database and logs (if requested)
if($createMountpointRoot -eq $true)
{
    CreateVolume "$ServerName-Exchange-MP" "1G" $dbRootDrive $dbDiskFolder $false
}
 
# Reset counters
$dbcount = 1
 
# Loop through total amount of databases for the server
do
{
    CreateVolume "$ServerName-DB$dbcount" $dbsize $dbmproot $dbDiskFolder $true
    
    "Creating mailbox database in Exchange 2010..."
    New-MailboxDatabase -Server $ServerName -Name "$ServerName-DB$dbcount" -EdbFilePath "$dbmproot\$ServerName-DB$dbcount\$ServerName-DB$dbcount.edb" -LogFolderPath "$dbmproot\$ServerName-DB$dbcount\Logs\"
    
    "Mounting new mailbox database..."
    Mount-Database -Identity "$ServerName-DB$dbcount"
    
    $dbcount ++ 
}
until($dbcount -eq $dbtotal + 1)
 
 
# Complete!
$ended = Get-Date
Write-Output "Volume Creation Complete!"
Write-Output "Started: $started"
Write-Output "Finished: $ended"
 
#############################
# END SCRIPT
#############################

There are a number of areas in which this script can be improved and that I will continue to work on. 

Exception handling is very important.  Understanding how your code could react in particular scenarios is difficult, but you don’t want your script to bomb out every time you run it either.  I’ve build quite a bit of exception handling into the mapping and mounting portions of the script, but this can always be reworked to be improved.

Categories: Compellent, Development, Exchange, PowerShell Tags:

Quick Hit: Export-Mailbox Cmdlet in Exchange 2007

September 8th, 2009 Justin Braun No comments

Ever needed to export the contents of an entire mailbox (or mailboxes)?  The Export-Mailbox cmdlet that is available via the Exchange 2007 PowerShell console can export each mailbox to a PST file.  In addition, Import-Mailbox can be used to import the contents of a PST back into an existing Exchange 2007 mailbox.

Syntax:

Export-Mailbox –Identity <mailbox alias> –PSTFolderPath <path to PST file>

There are a number of additional parameters available to filter the contents and tweet the configuration of what exactly is exported.  Using Export-Mailbox can be very time-consuming. a 200 MB mailbox can take 20 minutes or more (or did so in my tests).

A few things to remember when using Export-Mailbox:

  1. The account running the cmdlet must have full permissions to the mailbox in order for the export to complete successfully.

    MAPI Error Image

  2. The export will export all mail contents including the contents of the dumpster for that mailbox.
  3. Export-Mailbox requires that Outlook 2003 SP2 or later is installed on the machine from which the command is executed. 
  4. Export-Mailbox can only be run using the 32-bit version of the management tools.  This means that you have will have to load the 32-bit management tools on a workstation, since production environments of Exchange 2007 run in 64-bit environments.

This is a perfect alternative to what required ExMerge in the past.

Categories: Exchange, PowerShell, Quick Hits Tags:

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:

PowerShell v2 & Compellent Storage: What’s New?

July 17th, 2009 Justin Braun No comments

I contributed a couple of postings to the Compellent “Around The Block” blog this last week.  These postings cover the new features in PowerShell v2 and how they can be leveraged with the Compellent Storage Center Command Set.

Here are links to the postings:

Part 1

Part 2

You can download CTP3 of PowerShell v2 here.

Categories: Compellent, PowerShell, Storage 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: