The script creates a PDF report on all enabled and not expired user accounts, including their photos.
Note: The script relies on the MigraDoc module .NET library to generate PDF files. Before using the script, do the following:
- Download MiraDoc Foundation Libraries.
- Copy files located in the GDI+ folder of the downloaded ZIP archive to a certain folder on the computer where Adaxes Service is installed.
To schedule the report, create a scheduled task configured for the Domain-DNS object type that runs the script and assign it over any of your AD domains. To add the script to a scheduled task, use the Run a program or PowerShell script action.
Parameters:
- $migraDocDllPath - Specifies a path to the folder where files from the MiraDoc GDI+ library are located.
- $pdfFilePath - Specifies a path to the PDF file that will be created by the script.
- $header - Specifies the document header.
- $landscapeOrientation - Specifies document page orientation. Set the parameter to $True if you want landscape orientation or to $False to use portrait orientation.
- $fontSize - Specifies the font size in points.
- $fontName - Specifies the font face name.
- $columnMap - Specifies a map of column names and the corresponding properties of the user accounts. The properties must be specified by their LDAP display names. If you want a column to contain values from more than one property, specify a list of the properties you need separated by commas and enclosing each property in double quotes, for example: "Web Pages" = "wWWHomePage", "url".
- $columnOrder - Specifies the order in which the columns will appear in the document (left to right). Columns must be specified by their names as defined in $columnMap.
PowerShell
$migraDocDllPath = "C:\Scripts\MigraDoc" # TODO: modify me
# PDF File Settings
$pdfFilePath = "\\server\share\UserReport.pdf" # TODO: modify me
$header = "User List" # TODO: modify me
$landscapeOrientation = $True # TODO: modify me
$fontSize = 12 # TODO: modify me
$fontName = "Calibri" # TODO: modify me
$columnMap = @{
"Photo" = "thumbnailPhoto"
"First Name" = "givenName"
"Last Name" = "sn"
"Full Name" = "cn"
"Company" = "company"
"Address" = "streetAddress"
"Phone Numbers" = "telephoneNumber", "mobile", "otherHomePhone", "otherMobile"
} # TODO: modify me
$columnOrder = "Photo", "Full Name", "First Name", "Last Name", "Company", "Address", "Phone Numbers" # TODO: modify me
function GetObjectDisplayName($objectDN)
{
$objectPath = New-Object -TypeName "Softerra.Adaxes.Adsi.AdsPath" -ArgumentList @($null, $objectDN)
return [Softerra.Adaxes.Utils.ObjectNameHelper]::GetObjectName($objectPath, "IncludeParentPath")
}
function FindObjects($searhFilter, $propertiesToExport)
{
# Set search parameters
$searcher = $Context.BindToObject("Adaxes://rootDSE")
$searcher.SearchFilter = $searhFilter
$searcher.PageSize = 500
$searcher.SearchScope = "ADS_SCOPE_SUBTREE"
$searcher.SetPropertiesToLoad($propertiesToExport)
$searcher.VirtualRoot = $True
try
{
$searchResultIterator = $searcher.ExecuteSearch()
$searchResults = $searchResultIterator.FetchAll()
return ,$searchResults
}
finally
{
# Release resources used by the search
$searchResultIterator.Dispose()
}
}
function TooWide($string, $maxWidth)
{
$width = $xGraphics.MeasureString($string, $xfont).Width
return $width -gt $maxWidth.Point
}
# Build filter: enabled and not expired users
$currentDate = (Get-Date).ToFileTime()
$filter = "(&(sAMAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(|(accountExpires=>$currentDate)(accountExpires=0)(accountExpires=9223372036854775807)))"
# Get properties to export
$propertiesToExport = @()
$columnMap.Values | %%{$propertiesToExport += $_}
# Find objects and fetch properties
$searchResults = FindObjects $filter $propertiesToExport
if ($searchResults.Length -eq 0)
{
$Context.LogMessage("No users found", "Information")
return # Exit script
}
# Import MigraDoc DLLs
$files = Get-ChildItem -Path $migraDocDllPath
$files | %%{Import-Module $_.FullName}
# Create PDF Document
$document = New-Object MigraDoc.DocumentObjectModel.Document
$pageSize = [PdfSharp.PageSizeConverter]::ToSize([PdfSharp.PageSize]::A4)
if ($landscapeOrientation)
{
$document.DefaultPageSetup.Orientation = [MigraDoc.DocumentObjectModel.Orientation]::Landscape
$pageWidth = [MigraDoc.DocumentObjectModel.Unit]::FromPoint($pageSize.Height)
}
else
{
$document.DefaultPageSetup.Orientation = [MigraDoc.DocumentObjectModel.Orientation]::Portrait
$pageWidth = [MigraDoc.DocumentObjectModel.Unit]::FromPoint($pageSize.Width)
}
# Add empty section
$section = $document.AddSection()
$section.PageSetup.LeftMargin = [MigraDoc.DocumentObjectModel.Unit]::FromCentimeter(1)
$section.PageSetup.RightMargin = [MigraDoc.DocumentObjectModel.Unit]::FromCentimeter(1)
# Add paragraph and insert the header
$paragraph = $section.AddParagraph()
$paragraph.AddText($header)
$paragraph.Format.Font.Bold = $true
$paragraph.Format.Font.Size = 20
$paragraph = $section.AddParagraph()
# Add table
$table = $section.AddTable()
$table.Borders.Visible = $true
$table.Borders.Width = [MigraDoc.DocumentObjectModel.Unit]::FromCentimeter(0.01)
$point = [MigraDoc.DocumentObjectModel.Unit]::FromPoint($fontSize)
$font = New-Object MigraDoc.DocumentObjectModel.Font $fontName, $point
$table.Format.Font = $font
# Add columns
$columnIndex = @{}
$columnWidth = ($pageWidth.Centimeter - 2) / $columnMap.Count
for ($i = 0; $i -lt $columnOrder.Length; $i++)
{
$column = $table.AddColumn()
$column.Width = [MigraDoc.DocumentObjectModel.Unit]::FromCentimeter($columnWidth)
$columnName = $columnOrder[$i]
$columnIndex.Add($columnName, $i)
}
# Add table header
$tableHeader = $table.AddRow()
foreach ($columnName in $columnIndex.Keys)
{
$index = $columnIndex[$columnName]
$cell = $tableHeader.Cells[$index]
$cell.Format.Font.Bold = $True
[void]$cell.AddParagraph($columnName)
}
$tempFilesToRemove = @() # Temporary files with user pictures
try
{
# Create XGraphics object
$pdfDocument = New-Object PdfSharp.Pdf.PdfDocument
$page = $pdfDocument.AddPage()
$xGraphics = [PdfSharp.Drawing.XGraphics]::FromPdfPage($page)
# Create font object
$xfont = New-Object PdfSharp.Drawing.XFont $fontName, $fontSize
# Set cell parameters
$cellBordersWidth = [MigraDoc.DocumentObjectModel.Unit]::FromCentimeter(0.01)
$cellRightIndent = [MigraDoc.DocumentObjectModel.Unit]::FromCentimeter(0.2)
$cellLeftIndent = [MigraDoc.DocumentObjectModel.Unit]::FromCentimeter(0.2)
$availableWidth = [MigraDoc.DocumentObjectModel.Unit]::FromCentimeter($columnWidth -
$table.Borders.Width.Centimeter - $cellBordersWidth.Centimeter -
$cellRightIndent.Centimeter - $cellLeftIndent.Centimeter)
# Add user information to the table
foreach ($searchResult in $searchResults)
{
# Create row
$row = $table.AddRow()
foreach ($columnName in $columnMap.Keys)
{
# Get cell
$index = $columnIndex[$columnName]
$cell = $row.Cells[$index]
# Set cell settings
$cell.Borders.Width = $cellBordersWidth
$cell.Format.LeftIndent = $cellLeftIndent
$cell.Format.RightIndent = $cellRightIndent
# Get property values
$propertyName = $columnMap[$columnName]
$value = $NULL
if ($propertyName -is [System.Array])
{
$allValues = @()
foreach ($name in $propertyName)
{
$propertyValues = $searchResult.Properties[$propertyName].Values
if ($propertyValues -eq $NULL)
{
continue
}
$allValues += $propertyValues
}
if ($allValues.Length -ne 0)
{
$value = [System.String]::Join("; ", $allValues)
}
}
else
{
$values = $searchResult.Properties[$propertyName].Values
if ($propertyName -eq "thumbnailPhoto" -and $values -ne $NULL)
{
# Create temporary file
$tmpFilePath = [System.IO.Path]::GetTempFileName()
function ResizePhoto ($thumbnailPhotoBytes, $tmpFilePath)
{
$original = [System.Drawing.Image]$thumbnailPhotoBytes
if (($original.Height -lt 80) -and ($original.Width -lt 80))
{
# Write photo to file
[System.IO.File]::WriteAllBytes($tmpFilePath, $thumbnailPhotoBytes)
return
}
# Calculate the new size, preserve ratio
$ratioX = 80 / $original.Width
$ratioY = 80 / $original.Height
$ratio = $ratioY
if ($ratioX -le $ratioY)
{
$ratio = $ratioX
}
# Resize the photo to fit in the cell
[int]$newWidth = $original.Width * $ratio
[int]$newHeight = $original.Height * $ratio
$picture = New-Object System.Drawing.Bitmap($newWidth, $newHeight)
$graph = [System.Drawing.Graphics]::FromImage($picture)
$graph.Clear([System.Drawing.Color]::White)
$graph.DrawImage($original, 0, 0, $newWidth, $newHeight)
# Write photo to file
$picture.Save($tmpFilePath)
}
ResizePhoto $values[0] $tmpFilePath
# Add photo to PDF cell
$image = $cell.AddImage($tmpFilePath)
$tempFilesToRemove += $tmpFilePath
continue
}
elseif ($values -ne $NULL)
{
$value = [System.String]::Join("; ", $values)
}
}
# Check value length
if (($value -ne $NULL) -and (TooWide $value $availableWidth))
{
# Split the value to fit within column width
$formatedString = New-Object "System.Text.StringBuilder"
$current = [System.String]::Empty
foreach ($char in $value.ToCharArray())
{
$newString = $current + $char
if (TooWide $newString $availableWidth)
{
[void]$formatedString.Append($current)
[void]$formatedString.Append([MigraDoc.DocumentObjectModel.Chars]::CR)
$current = $char.ToString()
}
else
{
$current += $char
}
}
[void]$formatedString.Append($current)
$value = $formatedString.ToString()
}
# Add value to cell
$cell.AddParagraph($value)
}
}
# Render the document
$renderer = New-Object MigraDoc.Rendering.PdfDocumentRenderer
$renderer.Document = $document
$renderer.RenderDocument()
# Write to file
$renderer.PdfDocument.Save($pdfFilePath)
# Remove temporary files
foreach ($path in $tempFilesToRemove)
{
Remove-Item -Path $path -Force -ErrorAction SilentlyContinue
}
}
finally
{
# Release resources
$pdfDocument.Dispose()
$xGraphics.Dispose()
}