Tag Archives: Powershell

Update Your CO

If you ever come across the fact that the address info in Office 365 does not reflect your AD in respect to the country then the subject probably tells you what to do.

We don’t have many but there are international moves that occur within the company. Not that long ago somebody attended us that their country information on the Office 365 portal didn’t match. This whilst his on premise information within Active Directory did.

It seems like the value that is used via Azure AD Connect is the value present in ‘co’ within Active Directory. When using the normal management tools if the value in the directory got updated it would change both ‘c’ as well as ‘co’ entries (both represent ‘country’). However if updates occur via scripting in Powershell if the value of ‘c’ got updated, the value of ‘co’ remained the same. So if you come across this you probably have an automation running somewhere as did we that changes address information when needed make sure you update both.

Password expiration notification 1.2

I was talking to a friend of mine also working in IT and somewhere during the conversation the topic of Windows 7 and balloon popups appeared. As those popups tend to get missed in a lot of cases and depending on how the network is secured this means in case of password expirations the user is locked out of the network. To counter the balloon popup issue we run a script on a daily basis that mails users that their password is about to expire. Took some time to rewrite the script we run so it is more flexible in such you can determine which OUs will be scanned and on what days before expiration a notification is being send. Enjoy.

Version 1.1 Update:
Two things are fixed which came to my attention. The first is if you only had a single OU to be scanned the script wouldn’t work. For those that know powershell it was easy to fix. Now there is a check to see if there is only a single OU entry and the script will behave correctly if there is. The second fix is to check if an account has an e-mail address attached to it, otherwise no mail can be send out. If logging is enabled an additional line will be added to the log file in case no e-mail address was found.

Version 1.2 Update:
Code requires version 3 of Powershell, on version 2 .count does not work.

#requires -version 3

<#
Program  : PWExpNoticeMailer.ps1 - Password Expiration Notice Mailer
Author   : Eugene Dullaard (https://eugene.dullaard.nl/?p=409)
Date     : 27-Aug-2013 - version 1.0
           - Initial Script
Update   : 22-Dec-2014 - version 1.1
           - Check for existence of emailaddress and add none-existence to logfile if enabled
           - Check for Array in case of a single line entry, script didn't work for single OU
Update   : 13-May-2015 - version 1.2
           - Changed the required version of powershell to 3.0 due to use of .count

To Do's before running this script in your environment:
- This script should be scheduled on a daily basis in order to run the check once a day.
- You should modify the values of $LogFile,$DaysAdvWarn,$MailServer,$MailSender to what
  is required in your environment
- Change and/or add Organizational Units to be searched
- Change and/or add the above OUs into $ArrOU
- Change the $Subject and $Message so they contain what you require
- For testing purposes, change $_.EmailAddress to your own so you see the results
#>

Import-Module ActiveDirectory

$LogFile = ".\PWExpNoticeMailer.log"       # Logfile location, use "" to disable logging
$DaysAdvWarn = 21,14,7,4,2,1               # Days before expiration to send warning
$MailServer = "mailserver.domain.tld"      # FQDN or IP of mailserver to be used
$MailSender = "itnotification@domain.tld"  # Mail address of sender

# Enter Organizational Units to search for user accounts in LDAP notation
# Name them as you wish and use these names in ArrOU variable as per example
$ou0 = "OU=Abusers,OU=Beavis Corp,DC=domain,DC=tld"
$ou1 = "OU=Loosers,OU=Butthead Inc,DC=domain,DC=tld"

# Build the array of Organizational Units stated above, keep them in sync if you want to use them all
$ArrOU = $ou0,$ou1

# Get the maximum password age and exit if not set
$MaxPWAge = ((Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge).Days
if ($MaxPWAge -eq 0) {Throw "No Domain Password Policy Present"}

# Loop to run through all the Organizational Units to search
for ($a=0;$a -lt ($ArrOU).count;$a++) {

  #Check if Array exists and change searchbase properly in case of a single OU entry
  if ($ArrOU.count -eq 1) {$SearchBase = $ArrOU} else {$SearchBase = $ArrOU[$a]}

  #Get All Users within one of the Organizational Units
  #Not taking people that have disabled or non-expiring accounts
  Get-ADUser -Filter {(Enabled -eq $true) -and (PasswordNeverExpires -eq $false)} -SearchBase $SearchBase `
  -Properties PasswordLastSet,EmailAddress | ForEach-Object {

    # Set variables
    $GivenName = $_.GivenName
    $Name = $_.Name

    # Calculate days left before password change is required
    $PWTimeLeft = (($_.PasswordLastSet - (Get-Date)).Days)+$MaxPWAge

    #Check if Password Time Left equals Advance Warning Days
    if ($DaysAdvWarn -contains $PWTimeLeft) {

      # Add entry in logfile if enabled
      if ($LogFile -ne "") {Add-Content $LogFile "$Name,$PWTimeLeft"}

      # Build up message if mailaddress is present
      # You can add instructions or change the content as you see fit
      if ($_.EmailAddress -ne $null) {
        $Subject = "IT Notification: Your password expires in $PWTimeLeft days"
        $Message = "Dear $GivenName,

Your password is about to expire in $PWTimeLeft days.
Please take the appropiate action bofore your password expires.

Kind Regards,

Your IT Department

(This message is automatically generated, please contact your support desk if you have any questions.)
"

        # Sending Message (Hint: Change $_.EmailAddress to your own for testing purposes)
        Send-MailMessage -to $_.EmailAddress -from $MailSender -Subject $Subject -body $Message `
        -SmtpServer $MailServer
      }
      else {
        if ($LogFile -ne "") {Add-Content $LogFile "$Name,Failure : No Mail Address"}
      }
    }
  }
}

Q & A

Location of log-file?

In the example above the location is set to .\, which means current folder. If you are running this as a scheduled task without a folder set this usually ends up in the System32 folder.

Mail Statistics for specific OU

Got a request if we could deliver how much mail is sent and received for a certain group of users in a specific organization unit over time. For the received mail they wanted to see what came from external or internal. Created a script for this that either can get daily statistics so it can be automated/scheduled or by entering certain dates generate a one time only version. It uses the transactionlogs in Exchange and in a default deployment that means you can go back for only 30 days. This is used against an Exchange 2013 Organization, there has been no testing against older versions of Exchange Server. A scripting language isn’t the greatest tool to start crunching numbers as it tends to be slow. As such would not suggest to use this in a larger deployment but rather use tooling that is build for this task. In a small deployment and for the task I created it for it works fine. It will run in automated fashion for a few months providing data for a specific project to reduce mail within the company and then shut down again (until the exercise might be repeated)…

#requires -version 3

<#
Program  : ExchangeMailStatistics.ps1 - Get Exchange Send and DELIVER statistics from scanning the transactionlog
Author   : Eugene Dullaard (https://eugene.dullaard.nl/?p=685)
Date     : 02-Mar-2015
           - Initial Script

Warning:
- Better not use in large deployments or run it per server otherwise completion of script might take a day or more

To Do's:
- You should modify the values under 'User needs to modify below variables' so they represent
  the requirements and environment

This script will retrieve transaction logs from designated exchange servers and will retrieve how many mails have been
send or delivered for a specific OU in AD.

It will create temp files (which you can optionally keep) and some fixed files which are configurable within the script.
#>

#Fixed variables
$Date            = [STRING]((get-date -Format s).split("T"))[0]
                                                        #Sortable date of today for filenames
#====================================
#User needs to modify below variables
#====================================

$StartDate       = "02/20/2015 00:00:00"                #Start Date of TransactionLog Scan, ignore if $Scripted is enabled
$EndDate         = "02/21/2015 00:00:00"                #End Date of TransactionLog Scan, ignore if $Scripted is enabled.

$Scheduled        = $false                              #If true the above $StartDate and $Enddate are not used, instead
                                                        #it will calculate today-2 as $StartDate and today-1 as $EndDate.
                                                        #Script should run daily at the same time.

$CASServerPS     = "https://<cas-server>/powershell"    #Host for implicit remoting Exchange.
$MailboxServers  = "<mailbox-server1>","<mailbox-server2>","<mailbox-server3>"
                                                        #Mailbox Servers to scan TransactionLogs on.
$SenderDomain    = "@domain.tld"                        #Sender domain in use can be a part of a name as well (-match).
$UserSearchBase  = "OU=Users,OU=Corp,DC=domain,DC=tld"  #LDAP SearchBase for user accounts.
$KeepTempFiles   = $true                                #Keep per server transactionlogfiles for other/later use if $true

#===============
#Start of Script
#===============

#Override certain variables in case $Scripted is enabled
if ($Scheduled -eq $true) {
  $StartDate = [DateTime]::Today.AddDays(-3)
  $EndDate = [DateTime]::Today.AddDays(-2)
  $Year = $StartDate.Year ; $Month = "{0:00}" -f $StartDate.Month ; $Day = "{0:00}" -f $StartDate.Day
  $Date = "$Year-$Month-$Day"
}

#Outputdata
$FileSubmitTotals          = ".\totals.$Date.submit.csv"
$FileDeliverInternalTotals = ".\totals.$Date.internal.deliver.csv"
$FileDeliverExternalTotals = ".\totals.$Date.external.deliver.csv"

#Get User objects from AD
$UserData = (Get-ADUser -Filter * -SearchBase $UserSearchBase -Properties ProxyAddresses | Select-Object Name,ProxyAddresses)

#Initialize remoting to Exchange Server
#Import-PSSession (New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri $CASServerPS)

#==========================================================================
#Data-Collection and storage in CSV format for Submitted and DELIVERd mails
#==========================================================================

Write-Host "Data-retrieval" -ForegroundColor Green
Write-Host "=============="

#Create Datafile(s) to be parsed later, capturing send and DELIVER data per server and for usage beyond this script
#Per Server Data, so to scan if a user belongs to that server to avoid duplicate entries
$MailboxServers | ForEach-Object {

  $MailboxServer = $_
  Write-Host "$_ by SUBMIT"
  Out-File -InputObject "Sender" -FilePath .\$MailboxServer.$Date.submit.csv
  Get-MessageTrackingLog -Start $StartDate -End $EndDate -ResultSize Unlimited -EventID SUBMIT -Server $MailboxServer | `
    Select-Object Sender | ForEach-Object {
      $Sender = $_.Sender
      Out-File -InputObject "$Sender" -FilePath .\$MailboxServer.$Date.submit.csv -Append
    }
  Write-Host "$_ by DELIVER"
  Out-File -InputObject "Sender,Recipient" -FilePath .\$MailboxServer.$Date.deliver.csv
  Get-MessageTrackingLog -Start $StartDate -End $EndDate -ResultSize Unlimited -EventID DELIVER -Server $MailboxServer | `
    Select-Object Sender,Recipients | ForEach-Object {
      $Sender = $_.Sender
      $_.Recipients | ForEach-Object {
        Out-File -InputObject "$Sender,$_" -FilePath .\$MailboxServer.$Date.deliver.csv -Append
      }
    }
}

#===========================================================
#Data-Analysis SUBMIT for Name and amount of submitted mails
#===========================================================

Write-Host ""
Write-Host "Data-Analysis on SUBMIT" -ForegroundColor Green
Write-Host "======================="

$HashData = @{}         # Holds the name and amount of mails

$MailboxServers | ForEach-Object {
  Write-Host $_

  #Retrieve data for server from export file
  $Data = Import-Csv .\$_.$Date.submit.csv | Group-Object Sender | Select-Object Count,Name

  #Sanitize data so it discards non-user data and add to Output File
  $Data | ForEach-Object {
    $MailAddress = $_.Name
    #Get corresponding AD account
    $ADUser = ($UserData | Where-Object {$_.ProxyAddresses -match $MailAddress})
    #Check if account exists and if so add it to hash table using name and count
    if ($ADUser -ne $null) {
      if ($HashData[$ADUser.Name] -eq $null) {
        $HashData[$ADUser.Name] = $_.Count
      }
      else {
        $HashData[$ADuser.Name] = $HashData[$ADUser.Name] + $_.Count
      }
    }
  }
}

#Output collected data to CSV file
Write-Host ""
Write-Host "Writing SUBMIT data to $FileSubmitTotals"
$HashData.GetEnumerator() | ForEach-Object {New-Object -TypeName PSObject -Property @{Count=$_.Value;Name=$_.Name}} | `
  Export-CSV -Path $FileSubmitTotals -NoTypeInformation

#===========================================================
#Data-Analysis DELIVER for Name and amount of DELIVERd mails
#===========================================================

Write-Host ""
Write-Host "Data-Analysis on DELIVER" -ForegroundColor Green
Write-Host "========================"

$HashDataInternal = @{}       # Holds the Name and amount of internal sourced mails
$HashDataExternal = @{}       # Holds the Name and amount of external sourced mails

$MailboxServers | ForEach-Object {
  Write-Host $_

  #Retrieve MailboxUsers for Server
  $MailBoxUsers = (Get-Mailbox -Server $_ | Select-Object Name)

  #Retrieve data for server from export file
  $Data = Import-Csv .\$_.$Date.deliver.csv

  #Sanatize data to discard any non-user data and add to Output File
  $Data | ForEach-Object {
    $MailAddress = $_.Recipient

    #Get corresponding AD account
    $ADUser = ($UserData | Where-Object {$_.ProxyAddresses -match $MailAddress})
    if ($ADUser -ne $null) {

      #Check if user has a mailbox on this server
      $MailBoxUser = ($MailboxUsers | Where-Object {$_.Name -match $ADUser.Name})

      #After above checks add it to the internal or external hash table depending on sender
      if ($MailBoxUser -ne $null) {

        #Check if Sender is internal or external and add 1 to the appropiate hashtable
        if ($_.Sender -match $SenderDomain) {
          if ($HashDataInternal[$ADUser.Name] -eq $null) {
            $HashDataInternal[$ADUser.Name] = 1
          }
          else {
            $HashDataInternal[$ADuser.Name]++
          }
        }
        else {
          if ($HashDataExternal[$ADUser.Name] -eq $null) {
            $HashDataExternal[$ADUser.Name] = 1
          }
          else {
            $HashDataExternal[$ADuser.Name]++
          }
        }
      }
    }
  }
}

#Output collected data to CSV file
Write-Host ""
Write-Host "Writing internal DELIVER data to $FileDeliverInternalTotals"
$HashDataInternal.GetEnumerator() | ForEach-Object {New-Object -TypeName PSObject -Property @{Count=$_.Value;Name=$_.Name}} | `
  Export-CSV -Path $FileDeliverInternalTotals -NoTypeInformation
Write-Host ""
Write-Host "Writing external DELIVER data to $FileDeliverExternalTotals"
$HashDataExternal.GetEnumerator() | ForEach-Object {New-Object -TypeName PSObject -Property @{Count=$_.Value;Name=$_.Name}} | `
  Export-CSV -Path $FileDeliverExternalTotals -NoTypeInformation

#=================================================================
#Cleanup or rename of temp files depending on $KeepTempFiles value
#=================================================================
Write-Host ""
Write-Host "Cleanup" -ForegroundColor Green
Write-Host "======="
if ($KeepTempFiles -eq $false) {Write-Host "Deleting Temp Files"}
$MailboxServers | ForEach-Object {
  if ($KeepTempFiles -eq $false) {
    Remove-Item .\$_.$Date.submit.csv
    Remove-Item .\$_.$Date.deliver.csv
  }
}
Write-Host ""
Write-Host "End" -ForegroundColor Red
Write-Host "==="

Adding photos in AD

Within Active Directory of Microsoft there is a thumbnailPhoto entry where you can place a small photo. These pictures should preferably be 96 by 96 pixels and not larger than 10kb. You might create those photos from an already existing source and batch process them with something like Irfanview so they become the right size. There are several tools available to import them into Active Directory and they will also resize the picture, but it is possible to do this with PowerShell as well. The features are explained in the source code itself. In short it will read the pictures from a single source where the name of the picture should be similar to the logon name of the user and it will check if pictures are not exceeding the above mentioned limits. If successful the source picture is deleted, if not it is kept and a report is sent which can trigger a person to see what is wrong with the picture.

#requires -version 2

<#
Program  : ADPhotoImport.ps1
Author   : Eugene Dullaard
Date     : 14-Jul-2014
           - Initial Script

This script will import pictures from a source into Active Directory's
thumbnailPhoto field from a fixed content source.

Requirements:

Photo : 96x96 pixels and smaller than 10Kb
        filename in the format of <username>.jpg

Features :

- Content source is leading, it will search AD accounts by the name of the
  picture. The name without the extension of the picture should be matching
  SAMAccountName in Active Directory and can include dots, for example user
  account ab.user its picture should be ab.user.jpg.
- Check properties of picture before importing so that they comply with above
  requirements, wrong pictures will be added to an output report which is
  send after the script finishes.
- Remove photo once processed, keeping content source clean and allow for
  updating of existing pictures.
- If pictures exist and the corresponding account cannot be found this will
  be added to the output report. In this case the picture will not be removed.
- Runs with both a Content source as well as a Searchbase for the accounts.
- Searchbase will be searched recursively. Picture folder will not as it
  should delete the pictures once they've been processed. You should keep
  any existing source or have a seperate one if pictures need to be kept.
- Screen output for operator to see output while looking at the progress.
- Reporting errors will allow automated use of this script and corrective
  measures taken afterwards.
#>

#Variables (Change these to suit your environment)

$Content = "\\Server\Share\Folder"        # Content Source (Drive/UNC Path)
$Accounts = "OU=Users,DC=domain,DC=tld"   # User Accounts OU
$MailSender = "photoimport@domain.tld"    # Report Sent from mail address
$ReportAddress = "name@domain.tld"        # Report Sent to mail address
$SMTPServer = "mailserver.domain.tld"     # Mail server for relaying message

#Preliminaries

  Import-Module ActiveDirectory
  Add-Type -AssemblyName System.Drawing

  #Generate List of Photos in Content Source
  $Photos = Get-ChildItem $Content -Filter *.jpg

  #Report Header
  $Report = "Error report on ADPhotoImport
==============================
  
Maximum picture dimensions are 96 x 96 pixels, maximum size is 10Kb.

"
  $ReportCheck = $Report.Length           # Used to check for added entries

#Start processing the List of Photos (Main Loop)

$Photos | % {

  # Reset variables
  $ErrorStatus = $false
  $Basename = $_.BaseName
  Write-Host "==========================================="
  Write-Host "Processing : $Basename"

  # Check picture dimensions and size, if in error add to log and show on screen
  $jpg = New-Object System.Drawing.Bitmap $_.FullName
  if ($jpg.height -gt 96) {
    $Report = $Report + "$_ `t Pixel height exceeded.`n"
    Write-Host "Error..... : Pixel height exceeded" -ForegroundColor Red
    $ErrorStatus = $true
  }
  if ($jpg.width -gt 96) {
    $Report = $Report + "$_ `t Pixel width exceeded.`n"
    Write-Host "Error..... : Pixel width exceeded" -ForegroundColor Red
    $ErrorStatus = $true
  }
  if ($_.length -gt 10240) {
    $Report = $Report + "$_ `t Size limitation exceeded.`n"
    Write-Host "Error..... : File size exceeded" -ForegroundColor Red
    $ErrorStatus = $true
  }
  $jpg.Dispose()

  # Check for AD Account, if not existing add to log.
  $user = Get-ADUser -SearchBase $Accounts -Filter {(SAMAccountName -eq $BaseName)}
  if ($user -eq $null) {
    $Report = $Report + "$_ `t No AD user has been found.`n"
    Write-Host "Error..... : No matching user account" -ForegroundColor Red
    $ErrorStatus = $true
  }

  # If no errors are found, insert/replace picture and delete from content source
  if ($ErrorStatus -eq $false) {
    [byte[]]$photo = Get-Content $_.FullName -Encoding Byte
    Set-ADUser $_.BaseName -Replace @{thumbnailPhoto=$photo}
    Remove-Item $_.FullName
  }
 
} # End Main Loop

# Check Report Change, if changed send report
If ($Report.Length -ne $ReportCheck) {
  Send-MailMessage -From $MailSender -To $ReportAddress -Subject "AD Photo Import Error Report" `
    -SmtpServer $SMTPServer -Body $Report
}

Update: In order to see which accounts do not have a thumbnail photo you can enter the following command in PowerShell:

Get-ADUser -Filter * -SearchBase "OU=Users,DC=domain,DC=tld" -properties thumbnailPhoto | ? {!$_.thumbnailPhoto} | select Name,SAMAccountName

If you want to see a list of who has a thumbnail photo remove the ‘!’ in the code line above.

Android Mail vs Exchange

Ran into an issue that the native mail client couldn’t be configured on an Android device (specifically a Galaxy S4 from Samsung with either a version 4.3 or 4.4 of Android on it). The server it should connect to is an older Exchange 2007 environment (UR11 at the time this was happening). When trying it always ended up with the remark that the supplied username and password where invalid. Looking into the configuration of the mail client there is a DeviceID mentioned starting with SEC and then a certain amount of hexadecimal numbers, while in Active Directory I found a DeviceID that started with androidc. Through the aid of powershell I added the SEC DeviceID as found in the mail profile of the phone, waited a few moments and then applied the settings again and this time it picked it up and is working. Below is a small script to add a DeviceID into Exchange if you have the same issue, just supply the Exchange Alias together with the DeviceID from the phone and it might work for you too. If it is more permanent I’ll update it with a nicer version that includes error handling and comments. Needless to say there needs to be some time spend on why this is happening.

$Alias = Read-Host "Exchange Alias ......... : "
$DevID = Read-Host "New ActiveSync Device ID : "
$DData = (Get-CASMailbox -Identity $alias).ActiveSyncAllowedDeviceIDs
$DData = $DData+$DevID
Set-CASMailbox -Identity $Alias -ActiveSyncAllowedDeviceIDs $DData
Write-Host "Written ActiveSyncDeviceID $DevID to alias $alias."