Friday, March 18, 2022

Run powershell specific function from task scheduler

Executable is powershell.exe. Arguments:

-command "& { . "c:\location\to\script.ps1"; my_function_name }"

Powershell speed hacks

Powershell can be painfully slow when dealing with larger arrays, reading files and listing large directories. Here are some workarounds.

Arrays
Slow:

$myarray = @()
foreach ($x in $y) {
  $myarray += $x
}

Much faster is working with an arraylist:

$myarray = [System.Collections.ArrayList]@()
foreach ($x in $y) {
  $null = $procarray.Add($x)
}

Reading files
Slow:

get-content $filename

Fast:

([System.IO.File]::ReadAllLines($filename))

Listing large directories
Slow:

$items = get-item "\\server\share\*.csv" | sort LastWriteTime

The fastest workaround i’ve been able to find is actually using a dos prompt. Use dir switches for sorting purposes.
Note: dir returns just text, while get-items returns objects with all sorts of properties. It depends on your use case whether this hack is actually usable or not.

$items = cmd /r dir "\\server\share\*.csv" /OD /B

Friday, July 18, 2014

ParentContainer

Quest active directory powershell module has this nice property for user and computer objects: ParentContainer
Microsoft’s native ActiveDirectory module doesn’t.
I’m using this property a lot because it looks much more friendly than the CanonicalName.
Here’s a simple function to achieve the same.

function CanonicalName_to_ParentContainer ($cname)
{
  try
  { 
    $lastslash = $cname.lastindexof("/")
    $cname.substring(0,$lastslash)
  }
  catch
  {
    $cname
  }
}

Tuesday, December 10, 2013

Powershell IP address computations

Very neat function for powershell ip computations:
source: technet

function Get-IPrange 
{ 
<#  
  .SYNOPSIS   
    Get the IP addresses in a range  
  .EXAMPLE  
   Get-IPrange -start 192.168.8.2 -end 192.168.8.20  
  .EXAMPLE  
   Get-IPrange -ip 192.168.8.2 -mask 255.255.255.0  
  .EXAMPLE  
   Get-IPrange -ip 192.168.8.3 -cidr 24  
#>  
  
  param  
  (   
    [string]$start,  
    [string]$end,  
    [string]$ip,  
    [string]$mask,  
    [int]$cidr  
  )  
  
  function IP-toINT64 () {  
    param ($ip)  
  
    $octets = $ip.split(".")  
    return [int64]([int64]$octets[0]*16777216 +[int64]$octets[1]*65536 +[int64]$octets[2]*256 +[int64]$octets[3])  
  }  
  
  function INT64-toIP() {  
    param ([int64]$int)  
 
    return (([math]::truncate($int/16777216)).tostring()+"."+([math]::truncate(($int%16777216)/65536)).tostring()+"."+([math]::truncate(($int%65536)/256)).tostring()+"."+([math]::truncate($int%256)).tostring() ) 
  }  
  
  if ($ip) {$ipaddr = [Net.IPAddress]::Parse($ip)}  
  if ($cidr) {$maskaddr = [Net.IPAddress]::Parse((INT64-toIP -int ([convert]::ToInt64(("1"*$cidr+"0"*(32-$cidr)),2)))) }  
  if ($mask) {$maskaddr = [Net.IPAddress]::Parse($mask)}  
  if ($ip) {$networkaddr = new-object net.ipaddress ($maskaddr.address -band $ipaddr.address)}  
  if ($ip) {$broadcastaddr = new-object net.ipaddress (([system.net.ipaddress]::parse("255.255.255.255").address -bxor $maskaddr.address -bor $networkaddr.address))}  
  
  if ($ip) {  
    $startaddr = IP-toINT64 -ip $networkaddr.ipaddresstostring  
    $endaddr = IP-toINT64 -ip $broadcastaddr.ipaddresstostring  
  } else {  
    $startaddr = IP-toINT64 -ip $start  
    $endaddr = IP-toINT64 -ip $end  
  }   
  
  for ($i = $startaddr; $i -le $endaddr; $i++)  
  {  
    INT64-toIP -int $i  
  } 
 
}

Monday, May 13, 2013

Dump Exchange mailbox permissions

A complete script to first dump all exchange mailboxes to .csv and then enumerate all mailbox permissions.
It uses the Exchange 2010 management shell and Quest’s Active Directory Powershell modules.

Usage:

  • Load the script in the ISE editor.
  • Set the two global parameters
  • Run the script
  • first execute: dump_mailboxes (this wil generate a .csv with all mailboxes)
  • then execuite: dump_all_mailbox_permission (this will generate a second .csv with all permissions. Open in Excel to filter)
echo "-"

$global_ad_domain = "AD.CUSTOMER.LOCAL"
$global_ad_short = "AD"

### Load Modules for Active Directory and Exchange 2010
if (!($QUEST_LOADED))
{
  Add-PSSnapin Quest.ActiveRoles.ADManagement
  Set-QADPSSnapinSettings -DefaultSizeLimit 0

  $logged_on_to = $env:USERDNSDOMAIN
  if (!($logged_on_to -eq "$global_ad_domain"))
  {
    $user = read-host "Enter username in adusername format"
    $pw = read-host "Enter password" -AsSecureString
    connect-QADService -service '$global_ad_domain' -ConnectionAccount $user -ConnectionPassword $pw
  }
  else
  {
    connect-QADService
  }
  
  Set-QADProgressPolicy -ShowProgress $false
  $QUEST_LOADED=$TRUE
  echo "quest loaded"
}

if ($EMS_loaded -eq $NULL)
{
  . 'C:\Program Files\Microsoft\Exchange Server\V14\bin\RemoteExchange.ps1'
  echo "- Exchange Management Shell Loaded"
  Connect-ExchangeServer -auto
  $EMS_loaded = $true
  echo "- Exchange Management Shell Connected"
}

### Functions

function dump_mailboxes
{
  $output_file = "d:\temp\mailboxes.csv"
  echo "Name`tAlias" >$output_file
  # $mailboxes = Get-Mailbox -RecipientTypeDetails SharedMailbox
  $mailboxes = Get-Mailbox -resultsize Unlimited
  foreach ($mailbox in $mailboxes)
  {
    $Name = $mailbox.Name
    $Alias = $mailbox.Alias
    echo "$Name`t$Alias" >>$output_file
  }
}

function dump_all_mailbox_permission
{
  $output_file = "d:\temp\mailbox_permissions.csv"
  $lijst = import-csv -delimiter "`t" d:\temp\mailboxes.csv
  $aantal = $lijst.count
  $teller = 0
  write-host "Aantal functionele mailboxen: $aantal"
  echo "Mailbox`tAuthType`tGroup`tSam`tType" >$output_file  
  foreach ($regel in $lijst)
  {
    $teller++
    $Alias = $regel.alias
    write-host "$teller / $aantal -> $Alias"
    mailbox_permissions $Alias >>$output_file
  }
}

function mailbox_permissions($mailbox)
{
  if ($perms = get-mailboxpermission -identity "$mailbox" | where {($_.isinherited -eq $false) -and ($_.User -like "$global_ad_short\*")})
  {
    foreach ($perm in $perms)
    {
      $usr = $perm.User.tostring()
      $typeusr = (get-qadobject -identity $usr -DontUseDefaultIncludedProperties).type
      $usr = $usr.replace("$global_ad_short","")
      $rights = $perm.AccessRights
      if ($typeusr -eq "group")
      {
        $members = get-qadgroupmember -identity "$usr"
        foreach ($member in $members)
        {
          $mbmrsam = $member.samaccountname
          echo "$mailbox`t$typeusr`t$usr`t$mbmrsam`t$rights"
        }      
      }
      else
      {
        echo "$mailbox`t$typeusr`t`t$usr`t$rights"
      }
    }
  }
}

echo "-"

Wednesday, September 5, 2012

get size of directories with powershell, the stupid but fast way

All those ways to get the size of directories with powershell are extremely slow. Especially on network shares.
e.g.

$colItems = (Get-ChildItem C:Scripts | Measure-Object -property length -sum)
"{0:N2}" -f ($colItems.sum / 1MB) + " MB"

Currently i’m harvesting through roughly 40TB of data and it’s taking me daaaaaaaaaays!
So i’m in desperate need of something faster.
Then i thought about robocopy. Robocopy gives great statistics. So if i do a “dry-run” (list-only, not really copy), i might get the information i need by parsing the output.

Choice of switches:

  • /b = backup mode. Supposed to give me access to every file
  • /l = list only/dry-run, not really doing the copy
  • /mir = action what you would normally do when you would copy the data. This also dives into all subdirectories.
  • /r:0 = no retries
  • /w:0 = don’t wait on anything
  • /ns /nc /nfl /ndl /njh = no logging of any kind. We only want the summary.

Then we get this piece of code (it could be a lot shorter, but i’m keeping it readable):

function get_size_of_dir_in_bytes_with_robocopy ($directory)
{
  write-host "- $directory" -foreground "GREEN"
  [string]$result = robocopy /b /l /mir "$directory" "c:\whatever" /r:0 /w:0 /ns /nc /nfl /ndl /njh /bytes
  if (!($lastexitcode -eq 16))
  {
    $pos = ($result).indexof("Bytes : ")
    $start = $pos + 8
    $length = $result.length
    $end = $length - $start
    $newstring = ($result).substring($start,$end)
    $newstring = $newstring.trim()
    echo $newstring.split()[0]
  }
  else
  {
    echo "CANNOT ACCESS"
  }
}

Wednesday, June 20, 2012

powershell and robocopy

Hmm seems like $lastexitcode is a builtin variable. Nice!

robocopy "\\serverA\shareA" "\\serverB\shareB" /MIR /R:0 /W:0 /MT:4 /NP /LOG:"d:\logs\shareA_to_shareB.log" | out-null
interpret_robocopy_error $lastexitcode

and the function interpret_robocopy_error could be something quick’n'dirty like this:

function interpret_robocopy_error ([int]$errorlevel)
{
  if ($errorlevel -eq 16) { echo "   - Robocopy - ***SERIOUS FATAL ERROR*** "}
  if ($errorlevel -eq 15) { echo "   - Robocopy - OKCOPY + FAIL + MISMATCHES + XTRA "}
  if ($errorlevel -eq 14) { echo "   - Robocopy - FAIL + MISMATCHES + XTRA "}
  if ($errorlevel -eq 13) { echo "   - Robocopy - OKCOPY + FAIL + MISMATCHES "}
  if ($errorlevel -eq 12) { echo "   - Robocopy - FAIL + MISMATCHES "}
  if ($errorlevel -eq 11) { echo "   - Robocopy - OKCOPY + FAIL + XTRA "}
  if ($errorlevel -eq 10) { echo "   - Robocopy - FAIL + XTRA "}
  if ($errorlevel -eq 9) { echo "   - Robocopy - OKCOPY + FAIL "}
  if ($errorlevel -eq 8) { echo "   - Robocopy - FAIL "}
  if ($errorlevel -eq 7) { echo "   - Robocopy - OKCOPY + MISMATCHES + XTRA "}
  if ($errorlevel -eq 6) { echo "   - Robocopy - MISMATCHES + XTRA "}
  if ($errorlevel -eq 5) { echo "   - Robocopy - OKCOPY + MISMATCHES "}
  if ($errorlevel -eq 4) { echo "   - Robocopy - MISMATCHES "}
  if ($errorlevel -eq 3) { echo "   - Robocopy - OKCOPY + XTRA "}
  if ($errorlevel -eq 2) { echo "   - Robocopy - XTRA "}
  if ($errorlevel -eq 1) { echo "   - Robocopy - OKCOPY "}
  if ($errorlevel -eq 0) { echo "   - Robocopy - No Change "}
}

Wednesday, August 17, 2011

test for files or directories exceeding Windows MAX_PATH (v2)

Oops, tested the previous script on a samba server. For some reason, testing the script on a Windows 2008 R2 domain resulted in an exception. So here´s the new script.
Check will output warnings in red to your screen, all the rest of the data will go to the logfile.

For best results, export to a .csv and open in excel. Then sort the first column.

Calling the script:

path_depth_analysis "G:mydirectory" >c:output.csv

The script:

function path_depth_analysis( $path )
{
  $items = get-childitem $path
  if (!($items.count) -eq 0)
  {
    foreach ($item in $items)
    {
      [int]$length_path = $path.length
      [int]$length_item = $item.name.length
      [int]$total_length = $length_path + $length_item
      if ($total_length -gt 240)
      {
        $item_name = $item.name
        write-host "! - $total_length - $path -> $item_name" -foreground RED
      }
      [string]$fullname = $item.FullName
      [string]$type = $item.GetType().Name
      if ($type -eq "FileInfo")
      {
        echo "$total_length;file;$fullname"
      }
      else
      {
        echo "$total_length;dir;$fullname"
        path_depth_analysis "$fullname"
      }
    }
  }
}

Friday, August 12, 2011

test for files or directories exceeding Windows MAX_PATH

this script doesn’t seem to work correctly in a Windows-Windows environment, please go to test for files or directories exceeding Windows MAX_PATH (v2)

This week i was reading about a customer that needed an analysis of files or directories that were “too long”. As you may or may not know: if the full path to a file exceeds 260 characters, you may be running into troubles, as Windows does not handle that particularly well.
Microsoft’s article on that: http://msdn.microsof … 365247(v=vs.85).aspx

So i was thinking, how hard can that be? Let’s start powershell and write down a couple of lines …..

$maxpath=260

function testmaxpath($source)
{
  $found_yet=0
  $items = get-childitem $source -recurse
  foreach ($item in $items)
  {
     $the_full_name = $item.fullname
     $the_length = [string]$item.fullname.length
     if ([int]$the_length -ge $maxpath)
     {
       write-host "$the_length $the_full_name" -foregroundcolor red
       $found_yet++
     }
  }
  echo "-----------------------------------"
  echo " found $found_yet files/directories"
  echo "-----------------------------------"
}

then just run it against a disk or share, e.g.

testmaxpath e:\data
or
testmaxpath \\192.168.1.5\share_x

That’ll give you a nice overview.

off topic:
if you really want to bug your system administrator, he’ll like this:

mkdir \\server\share\%username%\aaaaaaaaaaaaabbbbbbbbbbbbbbbbbbccccccccccccccccccddddddddddddddeeeeeeeeeeeeeffffffffffffffffgggggggggggggggghhhhhhhhhhhhhhhhhhiiiiiiiiiiiiiijjjjjjjjjjjjjjkkkkkkkkkkkkkkkklllllllllllllllllmmmmmmmmmmmmmnnnnnnnnnnnnnn
subst h: \\server\share\%username%\aaaaaaaaaaaaabbbbbbbbbbbbbbbbbbccccccccccccccccccddddddddddddddeeeeeeeeeeeeeffffffffffffffffgggggggggggggggghhhhhhhhhhhhhhhhhhiiiiiiiiiiiiiijjjjjjjjjjjjjjkkkkkkkkkkkkkkkklllllllllllllllllmmmmmmmmmmmmmnnnnnnnnnnnnnn
mkdir h:\aaaaaaaaaaaaabbbbbbbbbbbbbbbbbbccccccccccccccccccddddddddddddddeeeeeeeeeeeeeffffffffffffffffgggggggggggggggghhhhhhhhhhhhhhhhhhiiiiiiiiiiiiiijjjjjjjjjjjjjjkkkkkkkkkkkkkkkklllllllllllllllllmmmmmmmmmmmmmnnnnnnnnnnnnnn
subst i: h:\aaaaaaaaaaaaabbbbbbbbbbbbbbbbbbccccccccccccccccccddddddddddddddeeeeeeeeeeeeeffffffffffffffffgggggggggggggggghhhhhhhhhhhhhhhhhhiiiiiiiiiiiiiijjjjjjjjjjjjjjkkkkkkkkkkkkkkkklllllllllllllllllmmmmmmmmmmmmmnnnnnnnnnnnnnn
mkdir i:\aaaaaaaaaaaaabbbbbbbbbbbbbbbbbbccccccccccccccccccddddddddddddddeeeeeeeeeeeeeffffffffffffffffgggggggggggggggghhhhhhhhhhhhhhhhhhiiiiiiiiiiiiiijjjjjjjjjjjjjjkkkkkkkkkkkkkkkklllllllllllllllllmmmmmmmmmmmmmnnnnnnnnnnnnnn
subst j: i:\aaaaaaaaaaaaabbbbbbbbbbbbbbbbbbccccccccccccccccccddddddddddddddeeeeeeeeeeeeeffffffffffffffffgggggggggggggggghhhhhhhhhhhhhhhhhhiiiiiiiiiiiiiijjjjjjjjjjjjjjkkkkkkkkkkkkkkkklllllllllllllllllmmmmmmmmmmmmmnnnnnnnnnnnnnn

… and copy some files and set some weird acl’s on them.
Guess what will happen when he wants to delete those directories?
Oh boy, those were the days :)

Friday, March 18, 2011

powershell - set disk quota using wmi

The 2003 powershell goes on :-)
Again, there are no available modules so i had to create my own.
How to set disk quota for a certain user (in megabytes) on a certain Windows 2003 server on a certain drive? Here’s how:

function set_disk_quota($username, $quota_hard, $computername, $disk)
{
  # preferred quota mode is enabled+deny access to disk, which is type 2
  # for logging purposes only but still allow disk access, select type 1
  $default_quota_mode = "2"

  $query_quota_enabled_or_not = "select * from Win32_QuotaSetting where VolumePath='"+$disk+":\\'"
  $query_user = "select * from Win32_Account where name='"+$username+"'"
  $query_disk = "select * from Win32_LogicalDisk where DeviceID='"+$disk+":'"

  $quota_disk = get-wmiobject -query $query_quota_enabled_or_not -computername $computername
  if ($quota_disk.State -eq "0")
  {
    echo "CHECK - ERROR - state 0 = Quota not enabled on disk -$disk- of -$computername-"
    echo "setting quota"
    $quota_disk.State = $default_quota_mode
    $quota_disk.Put()
  }
  if ($quota_disk.State -eq "1")
  {
    echo "CHECK - WARNING - state 1 = Quota enabled on disk -$disk- of -$computername- but not access is not denied when over quota"
    echo "setting quota"
    $quota_disk.State = $default_quota_mode
    $quota_disk.Put()    
  }
  if ($quota_disk.State -eq "2")
  {
    echo "CHECK - OK - state 2 = Quota enabled on disk -$disk- of -$computername- and access is denied when over quota"
  }

  $objAccount = get-wmiobject -query $query_user
  $objDisk = get-wmiobject -query $query_disk
  $objquota = (new-object management.managementclass Win32_DiskQuota).CreateInstance()
  $objquota.User = $objAccount.Path.RelativePath
  $objquota.QuotaVolume = $objDisk.Path.RelativePath

  if ($quota_hard -eq "0")
  {
    $objquota.Delete()
    echo "Quota deleted for $username"
  }
  else
  {
    $objquota.Limit = [int]$quota_hard * 1024 * 1024 * 1
    # Set the warning level to 90% of the $hard_limit
    $objquota.WarningLimit = [int]$quota_hard * 1024 * 1024 * 0.9
    $objquota.put()
  }  
}

Thursday, March 3, 2011

exchange 2003 powershell - forwarding

Because i need to maintain lots of Exchange 2003 servers and there are no Exchange 2003 powershell modules, i’m writing my own.
Here’s a piece of code that handles the forwarding of mail for users.
As you know, forwarding mail for a mailbox/user involves creating a contact with an external smtp address in Active Directory. Next, that contact can be assigned to the mailbox and a forwarding mode can be selected.
This script will handle all of those functions for you.

The script uses two global variables (customize to match your own Active Directory and/or place where you want to create these forwarding contacts):

$FQDN=",DC=netherlands,DC=europe,DC=microsoft,DC=com"
$base_security_groups_container="CN=Users"

Here’s the code:

function set_forward_mail($username, $forwarding_mode, $forwarding_address)
{
  # forwarding_mode
  # 0 = forwarding disabled
  # 1 = forward without local delivery
  # 2 = forward with local delivery

  if ($forwarding_mode -eq "2")
    {
      if (!(get-qadobject -identity "$username (forwarded by PowershellApp)"))
      {
        # contact doesn't exist (yet). Create now
        New-QADObject -ParentContainer "$base_security_groups_container$FQDN" -type "contact" -name "$username (forwarded by PowershellApp)" -DisplayName "$username (forwarded by PowershellApp)" -ObjectAttributes @{Description="$username (forwarded by PowershellApp)";mail="$forward_address";targetAddress="SMTP:$forwarding_address";mailNickname="$username"+"_forwarded_by_PowershellApp";msExchHideFromAddressLists=$true}
        # Recipient Update Service will do the rest.
        # Set the forwarding mode, type 2
        $forward_user_dn = (Get-QADObject -identity "$username (forwarded by PowershellApp)" | Select-Object dn).dn
        set-qaduser -identity $username -objectAttributes @{deliverAndRedirect=$true;altRecipient=$forward_user_dn}
      }
      else
      {
        # contact DOES exist. Update
        set-qadobject -identity "$username (forwarded by PowershellApp)" -ObjectAttributes @{Description="$username (forwarded by PowershellApp)";mail="$forward_address";targetAddress="SMTP:$forwarding_address";mailNickname="$username"+"_forwarded_by_PowershellApp";msExchHideFromAddressLists=$true}
        # clear any old addresses in the list of addresses and make the new one primary
        get-qadobject -identity "$username (forwarded by PowershellApp)" | Clear-QADProxyAddress | Add-QADProxyAddress -Address "SMTP:$forwarding_address" -Primary
        # make sure the forwarding mode is correct, type 2
        $forward_user_dn = (Get-QADObject -identity "$username (forwarded by PowershellApp)" | Select-Object dn).dn
        set-qaduser -identity $username -objectAttributes @{deliverAndRedirect=$true;altRecipient=$forward_user_dn}
      }
    }
  if ($forwarding_mode -eq "1")
    {
      if (!(get-qadobject -identity "$username (forwarded by PowershellApp)"))
      {
        # contact doesn't exist (yet). Create now
        New-QADObject -ParentContainer "$base_security_groups_container$FQDN" -type "contact" -name "$username (forwarded by PowershellApp)" -DisplayName "$username (forwarded by PowershellApp)" -ObjectAttributes @{Description="$username (forwarded by PowershellApp)";mail="$forward_address";targetAddress="SMTP:$forwarding_address";mailNickname="$username"+"_forwarded_by_PowershellApp";msExchHideFromAddressLists=$true}
        # Recipient Update Service will do the rest.
        # Set the forwarding mode, type 2
        $forward_user_dn = (Get-QADObject -identity "$username (forwarded by PowershellApp)" | Select-Object dn).dn
        set-qaduser -identity $username -objectAttributes @{deliverAndRedirect=$false;altRecipient=$forward_user_dn}
      }
      else
      {
        # contact DOES exist. Update
        set-qadobject -identity "$username (forwarded by PowershellApp)" -ObjectAttributes @{Description="$username (forwarded by PowershellApp)";mail="$forward_address";targetAddress="SMTP:$forwarding_address";mailNickname="$username"+"_forwarded_by_PowershellApp";msExchHideFromAddressLists=$true}
        # clear any old addresses in the list of addresses and make the new one primary
        get-qadobject -identity "$username (forwarded by PowershellApp)" | Clear-QADProxyAddress | Add-QADProxyAddress -Address "SMTP:$forwarding_address" -Primary
        # make sure the forwarding mode is correct, type 2
        $forward_user_dn = (Get-QADObject -identity "$username (forwarded by PowershellApp)" | Select-Object dn).dn
        set-qaduser -identity $username -objectAttributes @{deliverAndRedirect=$false;altRecipient=$forward_user_dn}
      }
    }
  if ($forwarding_mode -eq "0")
    {
      if (!(get-qadobject -identity "$username (forwarded by PowershellApp)"))
      {
        # contact doesn't exist, just disable forwarding
        set-qaduser -identity $username -objectAttributes @{deliverAndRedirect=$false;altRecipient=""}
      }
      else
      {
        # contact DOES exist. disable forwarding and delete contact
        set-qaduser -identity $username -objectAttributes @{deliverAndRedirect=$false;altRecipient=""}
        Remove-QADObject -identity "$username (forwarded by PowershellApp)" -Force
      }
    }
}

Wednesday, December 22, 2010

exchange 2003 powershell - create mailbox

As i told before, it’s not that easy to make changes with powershell to your Exchange 2003 environment as it is nowadays with Exchange 2010.
Since it was pretty hard to read certain values from Active Directory, i’m doing a pretty nasty trick: copy the values from an existing user.

Here’s how to create a mailbox for an existing user:

function add_exchange2003_mailbox_for_user($username)
{
  # to keep it simple: let's copy the properties of a template user, e.g. Administrator, and create the mailboxes in the same database
  $template_user = "Administrator"
  $userproperties = get-qaduser -identity $template_user -IncludeAllProperties
  set-qaduser -identity $username -objectAttributes @{msExchHomeServerName=$userproperties.MsExchHomeServerName}
  set-qaduser -identity $username -objectAttributes @{mailnickname="$username"}
  set-qaduser -identity $username -objectAttributes @{mDBUseDefaults='TRUE'}
  set-qaduser -identity $username -objectAttributes @{homeMBD=$userproperties.homeMDB}
  # now the Recipient Update Service will do the rest ...
}

exchange 2003 powershell - add primary address

When you’re making changes to your active directory or exchange environment with powershell, it’s a piece of cake with Windows 2008 R2 and/or Exchange 2010. All the cmdlets are there by default.
But when you’re dealing with Windows 2003 and/or Exchange 2003, it’s a whole different story.
I will be posting some of my scripts for Windows 2003 and Exchange 2003 from now on.
Because Windows 2003 has no active directory powershell module, i’m using the Quest AD Templates for that purpose (highly recommended!).

Here’s how to change the primary address for an exchange 2003 user:

function set_exchange2003_primary_address($username, $primary_smtp_address)
{
  # lowercase the to-be-added address
  $primary_smtp_address = $primary_smtp_address.ToLower()
  
  # get current addresses
  $userinfo = get-qaduser -identity $username
  $new_proxyaddresses = $userinfo.ProxyAddresses

  # lowercase all the "SMTP:" entries
  foreach ($number in 0..($new_proxyaddresses.Count - 1) )
  {
    $address = $new_proxyaddresses[$number]
    $new_proxyaddresses[$number]=$address.Replace("SMTP:", "smtp:")
  }
  
  # Next, check if the to-be-added address is allready in the list
  $allready_in_list = $FALSE
  foreach ($number in 0..($new_proxyaddresses.Count - 1) )
  {
    $address = $new_proxyaddresses[$number].ToLower()
    $check = $address.CompareTo("smtp:$primary_smtp_address")
    if ($check -eq 0)
    {
      # address is found in the list. Make it PRIMARY
      $new_proxyaddresses[$number]=$address.Replace("smtp:", "SMTP:")
      $allready_in_list = $TRUE
    }
  }  
  # But if it's not found, add the new adress to the list as primary
  if ($allready_in_list -eq $FALSE)
  {
    $new_proxyaddresses += 'SMTP:'+$primary_smtp_address
  }

  # now write the addresses to active directory
  set-qaduser -identity $username -objectAttributes @{ProxyAddresses=$new_proxyaddresses}
}

Hmm..
Guess I should have payed more attention… Why not do this:

function set_exchange2003_primary_address($username, $primary_smtp_address)
{
  get-qaduser -identity $username | Add-QADProxyAddress -Address $primary_smtp_address -Primary
}

Monday, November 1, 2010

Export .pst files in Exchange 2010 SP1

In addition to the import procedures, here’s how to export to .pst files:

Add an import to the queue:

New-MailboxExportRequest -Mailbox p.puk -FilePath "\\FS01\data\Backup Mailbox (PST)\p.puk.pst"

And status:

Get-MailboxExportRequest

Friday, October 29, 2010

Import .pst files in Exchange 2010 SP1

SP1 for Exchange 2010 has a new approach for importing .pst files.
First of all make sure the group “Exchange Trusted Subsystem” has NTFS permissions on the folder which contains all the .pst files.
Second, make sure this folder has been shared. Exchange only accepts unc paths.
Third, you have to make a new role assignment to a security group.
Create a universal group called “Mailbox Import Export Access” in active directory and add the user who’s going to do the export. Next, from the Exchange Management Shell:

New-ManagementRoleAssignment -Name "Mailbox Import Export Access" -SecurityGroup "Mailbox Import Export Access" -Role "Mailbox Import Export"

All the preparations have been made.

To queue (yes queue!) the import for a user:

New-MailboxImportRequest -Mailbox p.puk -FilePath "\\FS01\data\Backup Mailbox (PST)\p.puk.pst"

You can repeat the line for other users or pipe a list of users to build a bigger queue.

The following command shows the queue and the status of all imports:

Get-MailboxImportRequest

or

get-mailboximportrequest | fl filepath,status

One drawback so far:
I haven’t been able to find a way to set the locale by default (was possible with non-SP1 by typing

-locale "nl-NL"

for Dutch users) which is really annoying since users will end up with “Inbox” instead of “Postvak In”, “Sent Items” instead of “Verzonden Items”, etc.
This can be overcome by logging in to the webmail before starting the import and set the default language from there (first question asked when logging in). But that’s still annoying.

Update 2011/03/25
I haven’t been able to test it yet but running the following command prior to the import might do the trick ….

Set-MailboxRegionalConfiguration p.puk -Language "nl-NL" -DateFormat "dd-MM-yyyy" -LocalizeDefaultFolderName:$true

or for all mailboxes

get-mailbox | set-mailboxregionalconfiguration -Language nl-NL -DateFormat "dd-MM-yyyy" -LocalizeDefaultFolderName:$true

Wednesday, September 1, 2010

powershell - import .pst files in exchange 2010 (pt2)

In addition to the previous article, you might want to add your default locale to the import-mailbox command.
e.g.

import-mailbox -identity "hugo" -pstfolderpath "c:\pstfiles" -locale "nl-NL"

Otherwise annoying duplicate folders like Inbox/Postvak IN, Calendar/Agenda, Drafts/Concepten, etc will appear.

Wednesday, August 25, 2010

powershell - import .pst files in exchange 2010

I haven’t figured out how to get proper resultcodes or errorhandling from the “Import-Mailbox” command, but you can use your transcript file for that. See my other post for that.

You’ll need some global vars, e.g.:

$your_import_file="c:\import\import.csv"
$folder_with_psts="C:\exmerge\primary database export files"

Then it’s as simple as this:

function import_mailboxes()
{
  $UserDetails=Import-Csv -delimiter ";" $your_import_file
  $count=0
  $found=0
  $notfound=0
  foreach($UD in $UserDetails)
  {
    $count++
    $username=$UD.Code.ToLower()
    $full_path_to_pst=$folder_with_psts + $username + ".pst"
    $FileExists = Test-Path $full_path_to_pst
    if ($FileExists)
    {
      write-host "$count - $username - Ready to import ($full_path_to_pst)" -ForegroundColor Green
      $found++
      Import-Mailbox -Identity $username -PSTFolderPath $folder_with_psts
    }
    else
    {
      write-host "$count - $username - No matching pst file found!" -ForegroundColor Red
      $notfound++
    }
  }
  write-host "Summary: Found (and hopefully successfully imported): $found, Not Found: $notfound"
}

Tuesday, August 10, 2010

powershell - mail-enable a user (exchange 2010)

This script uses the RemoteExchange calls for Exchange 2010:

. 'C:\Program Files\Microsoft\Exchange Server\V14\bin\RemoteExchange.ps1'
Connect-ExchangeServer -auto

And the function:

function enable_mailbox_for_existing_user([string]$username)
{
  $check = get-aduser -Filter { samAccountName -eq $username }
  if($check -eq $null)
  {
    write-host "- User does not exist - ERROR" -ForegroundColor Red
  }
  else
  {
    # seems like the user exists
    $mailbox_test = get-user $username | select recipienttype
    if ($mailbox_test.RecipientType -eq "userMailbox")
    {
      write-host "- User is allready mail-enabled - WARNING" -ForeGroundColor Yellow
    }
    if ($mailbox_test.RecipientType -eq "User")
    {
      Enable-Mailbox -Identity $username -Alias $username | Out-Null
      write-host "- Mailbox for user created - OK" -ForeGroundColor Green
    }
  }
}

powershell - add (a user to) a securitygroup

Again, you’ll need the Windows 2008 r2 ActiveDirectory module for this to work:

import-module ActiveDirectory

Some static variables:

$default_securitygroup_ou="OU=MySecurityGroups,"

And the functions:

function add_security_group([string]$StrGroupName)
{
  $check = get-adgroup -Filter { name -eq $StrGroupName }
  if($check -eq $null)
  {
    $ad_path = $default_securitygroup_ou + (get-addomain).distinguishedname
    New-ADGroup -Path $ad_path -name $StrGroupName -GroupScope Global -GroupCategory Security
    write-host "- Security Group created - OK" -ForeGroundColor Green
  }
  else
  {
    write-host "- Security Group allready exists" -ForeGroundColor Yellow
  }
}

function add_user_to_group([string]$username, [string]$security_group)
{
  $grp = get-adgroup -Filter { name -eq $security_group }
  if ($grp -eq $null)
  {
    write-host "- Security Group does not exist - ERROR" -ForeGroundColor Red
  }
  else
  {
    # group does exist, lets see if the users is allready a member
    $members = get-adgroupmember -Identity $security_group
    foreach ($mem in $members)
    {
      if($mem.samAccountName -eq $username)
      {
        $found = $true
      }
    }
    if ($found)
    {
      write-host "- User is allready a member of this Security Group - WARNING" -ForegroundColor Yellow
    }
    else
    {
      add-adgroupmember -identity $security_group $username
      write-host "- User succesfully added to Security Group - OK" -ForegroundColor Green
    }
  }
}

powershell - add user

In addition to the previous example it would be nice to create users from the .csv files.

You’ll need the Windows 2008 r2 ActiveDirectory module for this to work:

import-module ActiveDirectory

Also i have a couple of static variables:

$default_users_ou="OU=myusers,"
$ad_domain="my.domain.local"
$share_profiles="\\fileserver01\profiles"
$share_users="\\fileserver01\users"
$homeshare_drive="Z:"

And here we go:

function add_user([string]$username, [string]$plaintextpassword, [string]$group, [string]$givenname, [string]$surname, [string]$displayname, [bool]$enabled)
{
  # syntax: add_user f.deboer mypass$78 teacher "Boer, De" "Frank" "Boer, De, Frank" $true

  $check = get-aduser -Filter { samAccountName -eq $username }
  if($check -eq $null)
  {
    $user_password=ConvertTo-SecureString -string $plaintextpassword -asPlainText -Force
    $ad_user_path=$default_users_ou + (get-addomain).distinguishedname
    $loginscript=$group + ".bat"
    New-ADUser -Name $displayname -SamAccountName $username -UserPrincipal "$username@$ad_domain" -AccountPassword $user_password -CannotChangePassword $true -PasswordNeverExpires $true -Enabled $enabled -ProfilePath "$share_profiles\$username" -HomeDirectory $share_users\$username -HomeDrive $homeshare_drive -ScriptPath $loginscript -GivenName $givenname -Surname $surname -DisplayName $displayname -Path $ad_user_path
    write-host "- User Created - OK" -ForeGroundColor Green
  }
  else
  {
    write-host "- User allready exists" -ForeGroundColor Yellow    
  }
}

powershell - parse .csv file

Powershell is ideal for bulk operations, e.g. creating lots of users in active directory.

Say we have a .csv file that looks like this:

loginname;firstname;middlename;lastname;fullname;function;password
f.deboer;f;de;boer;Boer, de, Frank;teacher;mypass$78
r.deboer;r;de;boer;Boer, de, Ronald;teacher;123pass60
m.manager;m;;manager;Manager, Mike;manager;superpassw0rd

You might want to specify some global variables first.

$import_file="d:\import\importfile.csv"

Now let’s create a simple function to read the file.

function readcsvfile()
{
  $UserDetails=Import-Csv -delimiter ";" $import_file
  foreach($UD in $UserDetails)
  {
    $loginname=$UD.loginname
    $firstname=$UD.firstname
    $middlename=$UD.middlename
    $lastname=$UD.lastname
    $fullname=$UD.fullname
    $function=$UD.function
    $password=$UD.password

    echo "$username"
    echo "$password"
    # or something else you want to do with tis information
  }
}

powershell - windows 2008 r2’s ActiveDirectory module

# Modules
# - http://technet.microsoft.com/en-us/library/ee617195.aspx
import-module ActiveDirectory

powershell - log entire session

To log your entire powershell session to a file you can use the start-transcript and stop-transcript commands.

$mypowershellapplicationdir = "c:\myapp"
$logfiledate = (get-date).tostring("yyyyMMddHHssmm") + ".txt"
start-transcript -path $mypowershellapplicationdir\logs\$logfiledate

Stop-transcript will automatically be done when leaving powershell.

Wednesday, August 4, 2010

Import and Export .pst files Exchange 2010

Currently, Exchange 2010 SP1 is still in beta. Among other new features it’s not longer necessary to install Outlook 2010 (64 bit!!) on your Exchange 2010 server.
For now you have to.

To import or export .pst files you need to get the right Role Assignment. From the Exchange Management Shell:

New-ManagementRoleAssignment –Role “Mailbox Import Export” –User “Administrator”

Then it’s just a matter of:

Export-Mailbox Administrator -PSTFolderPath E:\

HOWEVER
If you have all the roles (Mailbox, Client Access and Hub Transport) on the same server, you’ll run into error:

Error occurred in the step: Approving object. An unknown error has occurred., error code: -2147221219

Solution:

  • Either move the Mailbox (+Outlook 2010 64bit) to a second Exchange server.
  • Or change the following:
    • adsiedit
    • connect to..
    • Well known Naming Context: Configuration
    • CN=Configuration,DC=your,DC=domain,DC=local
    • CN=Services,
    • CN=Microsoft Exchange,
    • CN=<Your Organization Name>,
    • CN=Administrative Groups,
    • CN=Exchange Administrative Group (FYDIBOHF23SPDLT),
    • CN=Databases,
    • CN=Mailbox Database 0123456789,
    • Now rightclick on “CN=Mailbox Database 0123456789” (left pane), properties
    • security tab
    • Grant the System user the following rights: Read, Administer information store, Allow Impersonation to Personal Exchange, Create named properties in the information store, Open mail send queue, Receive as, Send as, View information store status
    • Restart

This should be fixed in SP1 or even before that.

Grant full access on all Exchange 2010 mailboxes

From the Exchange Management Shell:

Get-Mailboxdatabase | Add-AdPermission -User Administrator -AccessRights GenericAll