Archive for the ‘ Windows PowerShell ’ Category

Search Exchange for mobile devices with IMEI

At work I have a bunch of HTC phones lieing around. Quite often I don’t have the password for the phone so normally I would have to send them to the vendor to get them unlocked. This is both time consuming and costly. With Exchange you have the posibility to use a recovery password to reset the phones password. So I developed this script that search for phones with a specific string in the IMEI no. The script displayes all matches, mailboxes associated with the phone and the recovery password if possible. Something like this:

Recovery password for ’35575701146176702′ belonging to John Wayne (jw) is: 81689180324104937573

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Add-PSSnapin -name Microsoft.Exchange.Management.Powershell.Admin
 
$searchString = Read-Host "Enter IMEI no. to search for:"
"Searching for IMEI containing '" + $searchString + "'..."
 
$mailboxes = Get-Mailbox
 
foreach( $mailbox in $mailboxes )
{
    $deviceStats = Get-ActiveSyncDeviceStatistics -Mailbox $mailbox -ShowRecoveryPassword $true
    foreach( $deviceStat in $deviceStats )
    {
        if( $deviceStat.DeviceIMEI -like "*" + $searchString + "*" )
        {
            "Recovery password for '" + $deviceStat.DeviceIMEI + "' belonging to " + $mailbox.DisplayName + " (" + $mailbox.SamAccountName + ") is: " + $deviceStat.RecoveryPassword
            break
        }
    }
}

Find orphaned accounts in Active Directory

As an administrator I would of course never forget to delete a computer account ;) , but if someone else should forget it quite a lot of orphaned accounts would exist in the Active Directory over time. So how would one detect accounts that hasn’t been used for a while? Well, again the answer is PowerShell. With this simple, little script below you are able to choose the age of the accounts you want displayed. Of course you could easily edit the script so it deleted the accounts right away by using Remove-QADObject. You could also use the Get-QADUser cmdlet to search for user accounts instead of computer accounts.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Add-PSSnapin -name Quest.ActiveRoles.ADManagement
 
$searchRoot = "domain.local/Workstations"
$age = 60 # in days
 
$accounts = Get-QADComputer -IncludedProperties LastLogonTimeStamp -SearchRoot $searchRoot | Sort-Object -Property LastLogonTimeStamp -Descending
$today = Get-Date
 
foreach( $account in $accounts )
{
    if( $account.LastLogonTimeStamp -eq $null )
    {
        Write-Host $account.Name "last logon: Never"
    }
    else
    {
        $res = $today - $account.LastLogonTimeStamp
        $days = $res.Days
 
        if( $days -gt $age )
        {
            Write-Host $account.Name "last logon:" $days "days ago"
        }
    }
}

Display large mailboxes in Exchange Server 2007

One thing is certain when you assign space to mailboxes. If the user is assigned 100MB she will use it, if the user is assigned 1000MB, she will use it. Unlike in Exchange Server 2003 you are not able to retrieve a list that displays the mailbox size from the Exchange Server 2007 management console. The script below displays the 50 largest mailboxes in your organization in descending order. Simply edit the script to reflect your needs.

1
2
3
Add-PSSnapin -name Microsoft.Exchange.Management.Powershell.Admin
 
Get-MailboxStatistics -Server "server"  | Sort-Object -Descending TotalItemSize | ft displayname,totalitemsize,ItemCount,StorageLimitStatus | Select-Object -first 50

Automatic Computernames with Microsoft Deployment Toolkit

Environment: The server is Windows Server 2008 Standard Edition x64 with WDS installed. Microsoft Deployment Toolkit 2010 is installed on the server and Windows Automated Installation KIT (AIK) aswell. MDT 2010 can be downloaded here.

If you are used to RIS or WDS, you know that these services gives you the opportunity to set a “New client naming policy” and also where to place the client account for the workstation during deployment. When you work with MDT things are a little different since you generate a Lite Touch WIM image and add that as a boot image in WDS. By doing this you only use WDS for PXE boot and download the WIM image. After that the client moves on and WDS is no longer needed by the client. At this stage deployment hasn’t really begun yet so setting a client naming policy and/or the OU in which you want the new computer account to be created does not have any affect at all. Chances are that if you have already tried to deploy a workstation using MDT, you are already familiar with the random computernames that are generated by MDT. I have googled quite a lot wihtout any luck finding a good solution. I wanted a solution which named the workstations automatically say WKS001, WKS002 etc. If a computeraccount would be removed from Active Directory the computername should be available for a new workstation.

The computernaming can be handled quite easily with a PowerShell script. However the default security configuration of PowerShell will not let you run the script from a network share. Also in order to be able to rename the workstation in question I would have to get around UAC.

To get around UAC during deployment I had to be a little creative. I created an Organizational Unit in Active Directory and blocked inheritance. Besides linking my WSUS policy to the OU (since I want to be able to install Microsoft patches during deployment), I also created a GPO that would deactivate UAC and lower the PowerShell security settings so I could easily execute my scripts and other commands/programs during the process. The following GPO settings were the ones I used:

GPO Settings

GPO Settings

And just to visualize how it could look like in the Group Policy Management MMC:

Organizational Unit and GPO structure Organizational Unit and GPO structure

So now that script and program execution is ready, the next step is to configure MDT to deploy the computers into the “Deploying” Organization Unit we just created. To do this open the ”Deployment Workbench” and open the Task Sequence you are using. Under the “OS Info” tab click  ”Edit Unattended.xml” button. This will open the answer file for your OS installation and here you will be able to configure which OU in which you want computer account to be created. In the “Answer File” windows browse to Unattend –> Components –> 4 specialize –> x86_Microsoft-Windows-UnattendedJoin_neutral –> Identification. In the “Identification Properties” window there is a setting called “MachineObjectOU”. Enter the “distinguishedName” of the Organizationl Unit. To find the “distinguishedName” just open “Active Directory Users and Computers” –> right clik the Organizational Unit and choose “Properties”. Next open the “Attribute Viewer” tab.

Now it is time to introduce the script that does all the work. I know it could be a bit more sofisticated, but it works. Remember to change the prefix of computer account and the OU to search in so it fits your needs. The script simply browses through the OU and looks for computer account with a specific prefix, in this case “PC”. Lets say 3 computer accounts are found: PC001, PC002, PC004. The script will discover that PC003 is not in use and will assign this name to the new computer. Of course this will be unsuccessfull if PC003 is located somewhere else in Active Directory. In that case you will have to modify the script. If the script doesn’t fine any available account names it will just select the next in line e.g. PC005. In this case I saved the script as “ComputerRename.ps1″ in the “Script” directory of my deployment share.

function GetComputerList($TargetDn)
{
# Locates all computer accounts that begins with "PC"
# Change the value for your needs
 ${tFilter} = ‘(&(objectClass=computer)(cn=PC*))’
 ${Searcher} = New-Object System.DirectoryServices.DirectorySearcher $tFilter
 ${Searcher}.SearchRoot = “LDAP://${TargetDn}”
 ${Searcher}.SearchScope = [System.DirectoryServices.SearchScope]::Subtree
 # Page size default in Active Directory is 1000
 ${Searcher}.PageSize = 1000
 $Results = $Searcher.FindAll()
 return $Results
}

# Change OU path for you needs
$computers = GetComputerList("OU=Workstations,DC=yourdomain,DC=local")
$current  = 0
$previous = 0
$new_name = $null

# Run through search result
foreach( $computer in $computers )
{
    $cn = "" + $computer.Properties['cn']
    $current = [int]$cn.Substring(6)

    # Check for avialable computername
    if( $current.CompareTo($previous+1).Equals( 1 ) )
    {
        $new_name = $previous+1
        break # no need to search anymore
    }

    $previous = $current
}

# If no available computername use next in line
if( !$new_name )
{
    $new_name = $current+1
}

# Prefix number with 0's if needed
if( $new_name.CompareTo(10).Equals(-1) )
{
    $new_name = "00" + $new_name
}
elseif( $new_name.CompareTo(100).Equals(-1) )
{
    $new_name = "0" + $new_name
}

# Prefix name with constant
# Change the constant "PC" for your needs
$new_name = "PC" + $new_name
$new_name

# Do rename
$oComputerSystem = Get-WmiObject win32_computersystem
$oComputerSystem.Rename( $new_name )

The last thing to do is to make sure the script will be executed as part of the Task Sequence. Again open the “Deployment Workbench” and the Task Sequence you are using. Click “Add” –> “General” –> “Run Command Line”. Give the task a name e.g. “Computer Rename”. In the “Command Line” textbox enter the following command and set it up so it runs under an account that has the appropiate rights to rename a computer account:

powershell.exe "%SCRIPTROOT%\RenameComputer.ps1"

Hope this was usefull.

Extract Operating Systems from Active Directory

Prerequisites: ActiveRoles Management Shell for Active Directory must be installed on the host running this script.

On a forum I saw someone in need of a script that could display the operating systems of all the computers in Active Directory. Also it could help when you have to tell Microsoft how many licenses you need to pay for. This is an easy one so why not? :)

First I want to load the snappin so I don’t have to open the ActiveRoles Management Shell everytime I want to run the script.

Add-PSSnapin -name Quest.ActiveRoles.ADManagement

Next I’m going to fetch all computer accounts from Active Directory. For that I’m using the ‘Get-QADComputer’ cmdlet. You can add the attribute ‘-SearchRoot’ if you don’t want to search the entire Active Directory.

'Fetching computer accounts from Active Directory. This may take a while...'
$computers = Get-QADComputer | Sort-Object Name
'' + $computers.length + ' computer accounts fetched from Active Directory.'
'-----------------------------'

The abvious thing to do now is to run through the computers array and check their operating system, but I need a structure to save the statistics. For that I’m going to use a hashtable. In that way I will be able to use the name of the operating system as the key. First I declare the hashtable.

$stats = @{}

And then I collect the stats.

foreach( $computer in $computers )
{
    $OSName = $computer.OSName
   
    if( $OSName -eq $null )
    {
        $OSName = "Unknown OS"
    }
   
    $stats[$OSName]++

    #$computer.Name + "`t" + $computer.OSName + "`t" + $computer.OSVersion
}

As I run through the array I’m checking to see if the operating system value is $null. This is the case for e.g. the virtual names of a cluster service. I’m just going to list those as an unknown operating system. The physical nodes themselves will have the operating system property set. You can uncomment the last line if you want to display information about every single computer account.

That last thing to do is to display the collected statistics. You can just echo the $stats array, but if you want to sort the output you need to use ‘GetEnumerator()’.

You should get an output similiar to this:

Name                           Value                                          
----                           -----                                          
Windows Vista™ Ultimate        1                                              
Windows 7 Ultimate             1                                              
Windows 2000 Server            2                                              
Windows Server® 2008 Enterp... 3                                              
Windows 7 Professional         3                                              
Windows Server® 2008 Standard  6                                              
Windows Vista™ Business        9                                              
Windows Vista™ Enterprise      11                                             
Windows 7 Enterprise           11                                             
Unknown OS                     24                                             
Windows 2000 Professional      26                                             
Windows Server 2003            52                                             
Windows XP Professional        321                                            

Please note that Windows Server 2003 isn’t devided into several categories like Windows Server 2008. So you don’t know how many is Standard or Enterprise Edition.

Here’s the entire script: Display Operating Systems

Bulk Attribute Change of Users in Active Directory

Prerequisites: ActiveRoles Management Shell for Active Directory must be installed on the host running this script.

As mentioned in my post “Detect Orphaned HomeDirectories and Roaming Profiles” I was given the task to migrate data from one file server to the other. Since the paths to homedirectories and roaming profiles would change I needed to come up with a solution to change the paths of hundreds of users. Of course the solution would be a powershell script.

First I want to load the snappin so I don’t have to open the ActiveRoles Management Shell everytime I want to run the script.

Add-PSSnapin -name Quest.ActiveRoles.ADManagement

Next I want to fetch the users from Active Directory. This is done with the ‘get-QADUser’ cmdlet.

$users = Get-QADuser -SearchRoot 'mydomain.local/Users' -SizeLimit 0 | Sort-Object Name

This will return an array of all the users in the searchroot (it also searches in subcontainers). I’m piping the result to the ‘Sort-Object’ cmdlet as I want to sort the array by the names of the users.

Now that the users have been fetched from Active Directory all that is left to do is to run through the array, change the different paths, and commit the changes to Active Directory.

for( $i = 0; $i -lt $users.length; $i++ )
{
    $oldHomeDirectory = ""
    $oldProfilePath = ""
    $oldTsHomeDirectory = ""
    $oldTsProfilePath = ""

    $user = $users[$i]

    if( $user.HomeDirectory -ne $null ) # only if HomeDirectory is filled in
    {
        $oldHomeDirectory = $user.HomeDirectory
        $user.HomeDirectory = $user.HomeDirectory -replace "\\\\server01\\homedirectories\$", \\server02\homedirectories$
    }
    if( $user.ProfilePath -ne $null ) # only if ProfilePath is filled in
    {
        $oldProfilePath = $user.ProfilePath
        $user.ProfilePath = $user.ProfilePath -replace "\\\\server01\\profiles\$", \\server02\profiles$
    }
    if( $user.TsHomeDirectory -ne $null ) # only if TsHomeDirectory is filled in
    {
        $oldTsHomeDirectory = $user.TsHomeDirectory
        $user.TsHomeDirectory = $user.TsHomeDirectory -replace "\\\\server01\\homedirectories\$", \\server02\homedirectories$
    }
    if( $user.TsProfilePath -ne $null ) # only if TsProfilePath is filled in
    {
        $oldTsProfilePath = $user.TsProfilePath
        $user.TsProfilePath = $user.TsProfilePath -replace "\\\\server01\\tsprofiles\$", \\server02\tsprofiles$
    }
    ...
}

The loop runs through the array and does a ‘search and replace’ on the user attributes I want to change. If you want to change anything else, here’s where you want to add it. Just type “get-QADUser -?” to see all the attributes you can change. Also I’m saving the paths in a new set of variables before I change them. The only reason for this is that I want to use them for output e.g. to a log file. The last thing to do is simply to commit the changes and write some output lines for logging purposes.

$user.commitchanges()
$user.DisplayName + " (" + $user.sAMAccountName + ") updated"
"HomeDir:`t"       + $oldHomeDirectory +   " >> " + $user.HomeDirectory
"ProfilePath:`t"   + $oldProfilePath +     " >> " + $user.ProfilePath
"TsHomeDir:`t"     + $oldTsHomeDirectory + " >> " + $user.TsHomeDirectory
"TsProfilePath:`t" + $oldTsProfilePath +   " >> " + $user.TsProfilePath
"----------------------------------------------"

Here’s the entire script: Bulk Attribute Change of Users in Active Directory

Detect Orphaned HomeDirectories and Roaming Profiles

Prerequisites: ActiveRoles Management Shell for Active Directory must be installed on the host running this script.

I was given the task to migrate data from one file server to the other. Some of the data were the homedirectories and profiles (both for Windows clients and Terminal Services) of all users in Active Directory. Even though this could be solved quite easily with e.g. DFS and file replication, it seemed like a good time to finally write that script I’ve always wanted to write. A script that helps me locate folders that weren’t deleted when a user had been deleted in Active Directory. This will also help me minimize wasted disk space in the future which shortens backup time and (maybe more important) restoration time in case of a server failure. So here we go…

First I want to load the snappin so I don’t have to open the ActiveRoles Management Shell everytime I want to run the script.

add-pssnapin -name Quest.ActiveRoles.ADManagement

As I don’t want to delete the orphaned folders right away I want to move them to a different location. By doing this I can easily restore the data in case of a mistake (of course I would back it up on tape before deleting it permanently after a few days and yes, Volume Shadow Copy could also be a good idea). Also I like the idea of moving the orphaned folders to the same folder so I can select properties of the top folder and see how much space I’ve saved this time. Therefore I declare 2 arrays with 3 paths each.

$paths         = "\\fileserver\e$\homedirectories\", "\\fileserver\e$\profiles\", "\\fileserver\e$\tsprofiles\"
$orphanedPaths = "\\fileserver\e$\orphaned\homedirectories\", "\\fileserver\e$\orphaned\profiles\", "\\fileserver\e$\orphaned\tsprofiles\"

I want to be able to run this script over the network so I’m using ‘e$’ in my paths. $paths contains the paths in which to look for orphaned folders. $orphanedPaths containts the paths to which I want to store the orphaned folders temporarily.

 Next I want to run through the subfolders of the different paths.

for( $i = 0; $i -lt $paths.length; $i++ )
{
    $directories = Get-ChildItem -Path $paths[$i]

    ...
}

By using the ‘Get-ChildItem’ cmdlet I get an array ‘$directories’ containing all the subfolders in the given path. I want to run through this array of folders and check if a user with a corresponding name exists in Active Directory. Obviously this would require you to use %username% in the path for homedirectories and romaing profiles when you create a user in Active Directory.

for( $j = 0; $j -lt $directories.Length; $j++ )
{
    $currentDirectory = $directories[$j]

    $index1 = $currentDirectory.Name.ToLower().IndexOf( ".mydomain" )
    $index2 = $currentDirectory.Name.ToLower().IndexOf( ".v2" ) # vista og win7 profiles

    $sAMAccountName = $currentDirectory.Name.ToLower()

    if( $index1 -gt 0 )
    {
        $sAMAccountName = $sAMAccountName.Remove( $index1 )
    }
    if( $index2 -gt 0 )
    {
        $sAMAccountName = $sAMAccountName.Remove( $index2 )
    }
    ...
}

In some cases the profile directories are suffixed with .v2 (for Windows Vista and Windows 7 profiles) or the NetBIOS name of your domain. I want to strip those suffixes from the folder name to get the sAMAccountName I wish to search for in Active Directory. Also I convert everything to lowercase for easier comparison. The last thing we need to do now is to search for an owner in Active Directory and move the current folder if the search fails.

$user = Get-QADUser -SamAccountName $sAMAccountName

if( $user -eq $null )
{
    $currentDirectory.MoveTo( $orphanedPaths[$i] + $currentDirectory.Name  )
}

That’s all there is to it.

Here’s the entire script: Detect Orphaned HomeDirectories and Roaming Profiles