#
Alan Kaplan, www.akaplan.com
This GUI script lets you export users with any selected attributes.
Not required: admin rights or ActiveDirectory module
v 2.7 12/24/21 public version
#>
Add-Type -assemblyname Microsoft.visualBasic
#This is used to ensure that input box is modal
#http://stackoverflow.com/questions/9978727/focus-window-created-by-powershell-script
$activateWindow = {
$isWindowFound = $false
while (-not $isWindowFound) {
try {
[microsoft.visualbasic.interaction]::AppActivate($args[0])
$isWindowFound = $true
}
catch {
Start-Sleep -Milliseconds 200
}
}
}
$desktop = [environment]::GetFolderPath('Desktop')
$UACcode = @'
using System;
///
/// Flags that control the behavior of the user account.
///
[Flags()]
public enum UserAccountControl : int
{
/// The logon script is executed.
SCRIPT = 0x00000001,
/// /// The user account is disabled.
DISABLED = 0x00000002,
/// /// The home directory is required.
HOMEDIR_REQUIRED = 0x00000008,
/// The account is currently locked out.
LOCKEDOUT = 0x00000010,
/// No password is required.
PASSWD_NOTREQD = 0x00000020,
/// The user cannot change the password.
///
/// Note: You cannot assign the permission settings of PASSWD_CANT_CHANGE by directly modifying the UserAccountControl attribute.
/// For more information and a code example that shows how to prevent a user from changing the password, see User Cannot Change Password.
//
PASSWD_CANT_CHANGE = 0x00000040,
/// The user can send an encrypted password.
ENCRYPTED_TEXT_PASSWORD_ALLOWED = 0x00000080,
///
/// This is an account for users whose primary account is in another domain. This account provides user access to this domain, but not
/// to any domain that trusts this domain. Also known as a local user account.
TEMP_DUPLICATE_ACCOUNT = 0x00000100,
/// /// This is a default account type that represents a typical user.
NORMAL_ACCOUNT = 0x00000200,
/// This is a permit to trust account for a system domain that trusts other domains.
INTERDOMAIN_TRUST_ACCOUNT = 0x00000800,
/// This is a computer account for a computer that is a member of this domain.
WORKSTATION_TRUST_ACCOUNT = 0x00001000,
/// This is a computer account for a system backup domain controller that is a member of this domain.
SERVER_TRUST_ACCOUNT = 0x00002000,
/// Not used.
Unused1 = 0x00004000,
/// Not used.
Unused2 = 0x00008000,
/// The password for this account will never expire.
DONT_EXPIRE_PASSWD = 0x00010000,
/// This is an MNS logon account.
MNS_LOGON_ACCOUNT = 0x00020000,
/// The user must log on using a smart card.
SMARTCARD_REQUIRED = 0x00040000,
///
/// The service account (user or computer account), under which a service runs, is trusted for Kerberos delegation. Any such service
/// can impersonate a client requesting the service.
///
TRUSTED_FOR_DELEGATION = 0x00080000,
///
/// The security context of the user will not be delegated to a service even if the service account is set as trusted for Kerberos delegation.
///
NOT_DELEGATED = 0x00100000,
/// Restrict this principal to use only Data Encryption Standard (DES) encryption types for keys.
USE_DES_KEY_ONLY = 0x00200000,
/// This account does not require Kerberos pre-authentication for logon.
DONT_REQUIRE_PREAUTH = 0x00400000,
///
/// The user password has expired. This flag is created by the system using data from the Pwd-Last-Set attribute and the domain policy.
///
PWD_EXPIRED = 0x00800000,
///
/// The account is enabled for delegation. This is a security-sensitive setting; accounts with this option enabled should be strictly
/// controlled. This setting enables a service running under the account to assume a client identity and authenticate as that user to
/// other remote servers on the network.
///
TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION = 0x01000000,
/// Partial Secrets Account
PARTIAL_SECRETS_ACCOUNT = 0x04000000,
/// AES Keys required
USE_AES_KEYS = 0x08000000
}
'@
Add-Type $UACcode
#Hash table to store group Info
$script:ADGroupCache = @{ }
#Hash table to store Manager Info
$ManagerHT = @{}
Enum eType {
DES_CBC_CRC = 0x01
DES_CBC_MD5 = 0x02
RC4_HMAC = 0x04
AES128_CTS_HMAC_SHA1_96 = 0x08
AES256_CTS_HMAC_SHA1_96 = 0x10
}
Function Get-SupportedEncryption ($UserEncType) {
switch ($UserEncType) {
$null {
#default when null is RC4
'[Empty] RC4_HMAC_MD5'
}
0 {
'[0] RC4_HMAC_MD5'
}
Default {
$encList = [System.Collections.Generic.List[string]]::new()
[enum]::getvalues([eType]) |
ForEach-Object {
if (($UserEncType -band [eType]::$_) -eq [eType]::$_) {
$EncList.add($_)
}
}
"[$UserEncType] " + ($encList -join (', ')).tostring()
}
}
}
Function Get-SaveFile($Prompt, $filter, $initialDirectory, [string]$defaultFile) {
add-type -assemblyname System.Windows.Forms
$SaveFileDialog = New-Object System.Windows.Forms.SaveFileDialog
$SaveFileDialog.initialDirectory = $initialDirectory
$SaveFileDialog.filter = $filter
$SaveFileDialog.Title = $Prompt
$SaveFileDialog.filename = $defaultFile
if ($saveFileDialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
$SaveFileDialog.filename
}
ELSE {}
}
#Convert ADSPath/DistinguishedName to Canonical path using strings only
Function Convert-DNtoCanonical($adspath) {
#Clean anything to left of CN=
$Cleaned = ([regex]::Replace($adspath, '^.*CN=', '')) -replace '\\', ''
$a = $Cleaned.split(',')
#$sCN = $a[0]
$sCN = $cleaned.substring(0, $cleaned.indexof('=') - 3)
$aMid = ($a | Where-Object { $_.Startswith('OU=') }) -replace ('OU=')
[array]::Reverse($aMid)
$sMid = $aMid -join '/'
$sDomain = ($a | Where-Object { $_.Startswith('DC=') }) -replace ('DC=') -join '.'
($sDomain, $sMid, $sCN) -join '/'
}
#Bill Stewart
function Invoke-Method([__ComObject] $object, [String] $method, $parameters) {
$object.GetType().InvokeMember($method, "InvokeMethod", $NULL, $object, $parameters)
}
Function Get-GroupNTName($GroupDN) {
if ($ADGroupCache.ContainsKey($GroupDN)) {
Write-Verbose "Found group in cache: $GroupDN"
$groupNT = $ADGroupCache[$GroupDN]
}
Else {
$groupNT = Convert-DNtoNTName $GroupDN
$ADGroupCache.Add($GroupDN, $groupNT)
}
$GroupNT
}
Function Get-GroupNames($ArrMembers) {
if (($ArrMembers).length -eq 0) {
$strGrpList = ''
}
Else {
$aGrpList = ForEach ($groupDN in $ArrMembers) {
([string]$(Get-GroupNTName $groupDN)).trim()
}
$strGrpList = $aGrpList -join ('; ')
}
$strGrpList
}
function Get-ManagerUPN($managerDN) {
if ($ManagerDN) {
if ($ManagerHT.ContainsKey($managerDN)) {
$mgrUPN = $ManagerHT[$ManagerDN]
}
Else {
$ManagerDom = Get-DomainFromDNString $managerDN
Try {
$oManager = [ADSI]"LDAP://$ManagerDom/$managerDN"
$mgrUPN = $oManager.properties['userPrincipalName'].value
$ManagerHT.Add($managerDN, $mgrUPN)
}
Catch {}
}
$mgrUPN
}
}
#This function converts a DN to a NT Name, removing the odd spaces sometimes appearing with nametranslate
Function Convert-DNtoNTName($strDN) {
$NameTranslate = New-Object -ComObject "NameTranslate"
Invoke-Method $NameTranslate "Init" (3, "")
Invoke-Method $NameTranslate "Set" (1, $strDN)
$retval = (Invoke-Method $NameTranslate "Get" (3))
$retval.ToString().trim()
[void][System.Runtime.Interopservices.Marshal]::ReleaseComObject($NameTranslate)
}
Function Get-DomainFromDNString($strADsPath) {
if ($strADsPath.startsWith('DC=')) {
($strADsPath.Replace("DC=", ".").Replace(',', '')).Substring(1)
}
Else {
$strADsPath.Substring($strADsPath.IndexOf(",DC")).Replace(",DC=", ".").Substring(1)
}
}
$i = 0
Add-Type -Assembly PresentationFramework
#Get members of group with selected properties
Function Select-PropertiesForm {
Param
(
# ObjectProps help description
[Parameter(Mandatory = $True,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true,
Position = 0)]
[Object[]]$objectProps,
# FormTitle help description
[Parameter(Mandatory = $False,
Position = 1)]
[string]$FormTitle = "Select in order the properties to report, then accept list",
# When True, Exit if cancel or close without selecting any properties. Default is continue
[Parameter(Mandatory = $False,
Position = 2)]
[Switch]$ExitOnCancel = $False
)
#Initial form code generated by SAPIEN Technologies PrimalForms (Community Edition) v1.0.10.0
Add-Type -assemblyname System.Windows.Forms
Add-Type -assemblyname System.Drawing
$script:NewList = ""
$SelectForm = New-Object System.Windows.Forms.Form
$CancelBtn = New-Object System.Windows.Forms.Button
$AcceptBtn = New-Object System.Windows.Forms.Button
$MoveLftBtn = New-Object System.Windows.Forms.Button
$MoveRtBtn = New-Object System.Windows.Forms.Button
$lb_NewList = New-Object System.Windows.Forms.ListBox
$lb_OrigList = New-Object System.Windows.Forms.ListBox
$lb_OrigList.Sorted = $true
$InitialFormWindowState = New-Object System.Windows.Forms.FormWindowState
#Event Script Blocks
$On_AcceptBtn_Click =
{
$script:NewList = @()
foreach ($item in $lb_NewList.Items) { $script:NewList += $item }
$SelectForm.Close()
}
$On_CancelBtn_Click =
{
$SelectForm.Close()
}
# Begin by loading data passed to function
$On_SelectForm_Load =
{
foreach ($item in $objectProps) { $lb_OrigList.items.add($item) }
}
#Click the move left button removes from New list and puts back item to the original list
$On_MoveLftBtn_Click =
{
$movelist = @()
foreach ($item in $lb_NewList.SelectedItems) { $movelist += $item }
foreach ($item in $movelist) {
$lb_NewList.items.remove($item)
$lb_OrigList.items.add($item)
}
}
#Click the move right button removes from original list and puts item to the new list
$On_MoveRtBtn_Click =
{
$movelist = @()
foreach ($item in $lb_OrigList.SelectedItems) { $movelist += $item }
foreach ($item in $movelist) {
$lb_OrigList.items.remove($item)
$lb_NewList.items.add($item)
}
}
$OnLoadForm_StateCorrection =
{ #Sapien says this corrects the initial state of the form to prevent the .Net maximized form issue
$SelectForm.WindowState = $InitialFormWindowState
}
$SelectForm.CancelButton = $CancelBtn
$SelectForm.AutoSize = $True
$SelectForm.StartPosition = "CenterScreen"
$SelectForm.Name = "SelectForm"
$SelectForm.Text = $FormTitle
$SelectForm.add_Load($On_SelectForm_Load)
#Cancel Button
$CancelBtn.DialogResult = 2
$CancelBtn.Location = New-Object System.Drawing.Point 255, 220
$CancelBtn.Name = "CancelBtn"
$CancelBtn.Size = New-Object System.Drawing.Size 75, 25
$CancelBtn.TabIndex = 5
$CancelBtn.Text = "Cancel"
$CancelBtn.UseVisualStyleBackColor = $True
$CancelBtn.add_Click($On_CancelBtn_Click)
$SelectForm.Controls.Add($CancelBtn)
#Accept Button
$AcceptBtn.Location = New-Object System.Drawing.Point 255, 175
$AcceptBtn.Name = "AcceptBtn"
$AcceptBtn.Size = New-Object System.Drawing.Size 75, 25
$AcceptBtn.TabIndex = 4
$AcceptBtn.Text = "Accept List"
$AcceptBtn.UseVisualStyleBackColor = $True
$AcceptBtn.add_Click($On_AcceptBtn_Click)
$SelectForm.Controls.Add($AcceptBtn)
#Move Left Button
$MoveLftBtn.Location = New-Object System.Drawing.Point 255, 110
$MoveLftBtn.Name = "MoveLftBtn"
$MoveLftBtn.Size = New-Object System.Drawing.Size 75, 45
$MoveLftBtn.TabIndex = 3
$MoveLftBtn.Text = "<< Move"
$MoveLftBtn.UseVisualStyleBackColor = $True
$MoveLftBtn.add_Click($On_MoveLftBtn_Click)
$SelectForm.Controls.Add($MoveLftBtn)
#Move Right Button
$MoveRtBtn.Enabled = $True
$MoveRtBtn.Location = New-Object System.Drawing.Point 255, 40
$MoveRtBtn.Name = "MoveRtBtn"
$MoveRtBtn.Size = New-Object System.Drawing.Size 75, 45
$MoveRtBtn.TabIndex = 2
$MoveRtBtn.Text = "Move >>"
$MoveRtBtn.UseVisualStyleBackColor = $True
$MoveRtBtn.add_Click($On_MoveRtBtn_Click)
$SelectForm.Controls.Add($MoveRtBtn)
#Left Panel original data
$lb_OrigList.FormattingEnabled = $True
$lb_OrigList.Location = New-Object System.Drawing.Point 10, 5
$lb_OrigList.Name = "listBox1"
$lb_OrigList.Size = New-Object System.Drawing.Size 235, 355
$lb_OrigList.TabIndex = 0
$lb_origList.SelectionMode = "multiExtended"
$SelectForm.Controls.Add($lb_OrigList)
#Right Panel new data
$lb_NewList.FormattingEnabled = $True
$lb_NewList.Location = New-Object System.Drawing.Point 345, 5
$lb_NewList.Name = "listBox2"
$lb_NewList.Size = New-Object System.Drawing.Size 235, 355
$lb_NewList.TabIndex = 1
$lb_NewList.SelectionMode = "multiExtended"
$SelectForm.Controls.Add($lb_NewList)
#Sapien says save the initial state of the form
$InitialFormWindowState = $SelectForm.WindowState
#Init the OnLoad event to correct the initial state of the form
$SelectForm.add_Load($OnLoadForm_StateCorrection)
#Show the Form
$SelectForm.ShowDialog() | Out-Null
#If they choose nothing or close, return all
if ($script:NewList[0].Count -eq 0) {
If ($ExitOnCancel) { Break }ELSE { $objectProps }
}
ELSE {
$script:NewList
}
}
Function ConvertTo-PSObjectFromDirectoryEntry {
Param
(
# oDS is a directory services object
[Parameter(Mandatory = $true,
ValueFromPipeline = $true,
Position = 0)]
[ValidateNotNullOrEmpty()]
$oDS,
# Optional list of parameters
[Parameter(Mandatory = $False,
ValueFromPipeline = $False,
Position = 1)]
$PropList
)
$outval = New-Object -TypeName PsObject
if ($null -eq $PropList) { $PropList = $oDS.Properties.PropertyNames }
foreach ($strProperty in $PropList) {
Write-verbose $strProperty
Try {
$val = $oDS.Properties[[string]$strProperty].Value
$basename = $val.gettype().basetype.name
switch ($basename) {
'Array' { $val = $val -join ', ' ; break }
'Object' { if ($val.GetTypeCode() -eq 'byte') { $val = ByteToString ($val) } ; break }
'MarshalByRefObject' {
if (($val -eq 0) -or ($null -eq $val) ) {
$val = ''
Break
}
Try {
$val = [datetime]::fromfiletime($oDS.ConvertLargeIntegerToInt64($val))
if ([datetime]::Parse($val).year -eq 1600) { $val = '' }
}
Catch {
$val = ''
}
break
}
Default { }
}
}
Catch {
$val = ''
}
Add-Member -InputObject $outval -MemberType NoteProperty `
-Name $strProperty -Value $val -force
}
$outval
}
Function ByteToString($v) {
$ADPropVal = ''
#Delim character is used to join string of bytes
$delim = ";"
if ($v.count -eq 1) {
$v.tostring()
}
Else {
For ($i = 0; $i -le $v.count - 1; $i++) {
if ($v.item($i)) {
$ADPropVal += $v.item($i).ToString() + $delim
}
}
}
#Cleanup to remove trailing delimter
$adpropval.TrimEnd($delim)
}
Function Get-SaveFile($Prompt, $filter, $initialDirectory, [string]$defaultFile) {
add-type -assemblyname System.Windows.Forms
$SaveFileDialog = New-Object System.Windows.Forms.SaveFileDialog
$SaveFileDialog.initialDirectory = $initialDirectory
$SaveFileDialog.filter = $filter
$SaveFileDialog.Title = $Prompt
$SaveFileDialog.filename = $defaultFile
if ($saveFileDialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
$SaveFileDialog.filename
}
ELSE { }
}
Function Select-ADOU {
[CmdletBinding()]
Param
(
# Title text
[Parameter(Position = 0)]
$TitleText,
# Instruction text is below title, and above domain box
[Parameter(Position = 1)]
[string]$InstructionText,
# Select Button Text
[Parameter(Position = 3)]
[string]$BtnText,
# Show Containers. Default is only OUs
[Parameter(Mandatory = $False)]
[Switch]$ShowContainers = $False,
# Force Single Domain. No navigation within forest
[Parameter(Mandatory = $False)]
[Switch]$SingleDomain = $False,
#Set the initial domain. This must be a FQDN, example Contoso.com
[Parameter(Mandatory = $False)]
[string]$InitialDomain,
#Set the StartingOU. This must be a dn, example 'OU=workstations,DC=Contoso,DC=com'
[Parameter(Mandatory = $False)]
[string]$StartOUdN,
#Use Checkboxes
[Parameter(Mandatory = $False)]
[switch]$ShowCheckBoxes = $False
)
### Helper functions ###
Function Show-NoCheck {
#Show the Form without CheckBoxes
$dialogResult = $Form.ShowDialog()
if ($dialogResult -eq [System.Windows.Forms.DialogResult]::OK) {
If ($null -ne $treeViewNav.SelectedNode) {
$OUName = [string]$treeViewNav.SelectedNode.Text
$OUDN = [string]$treeViewNav.SelectedNode.Tag
}
ELSE {
$OUName = [string]$treeViewNav.Nodes[0].Text
$OUDN = [string]$treeViewNav.Nodes[0].Tag
}
$RetVal = [PSCustomObject]@{
Domain = [string]$DomainBox.SelectedItem
OUName = $OUName
OUDN = $OUDN
}
$RetVal
$Form.Close()
}
}
#Sapien.com, see notes
Function Get-CheckedNodes {
param(
[ValidateNotNull()]
[System.Windows.Forms.TreeNodeCollection] $NodeCollection,
[ValidateNotNull()]
#[System.Collections.Generic.List][object]$CheckedNodes
[System.Collections.ArrayList]$CheckedNodes
)
foreach ($Node in $NodeCollection) {
if ($Node.Checked) {
[void]$CheckedNodes.Add($Node)
}
Get-CheckedNodes $Node.Nodes $CheckedNodes
}
}
Function Show-CheckBoxes {
#Show the Form
$dialogResult = $Form.ShowDialog()
if ($dialogResult -eq [System.Windows.Forms.DialogResult]::OK) {
$CheckedNodes = New-Object System.Collections.ArrayList
Get-CheckedNodes $treeViewNav.Nodes $CheckedNodes
if ($CheckedNodes.count -gt 0) {
$RetVal = foreach ($node in $CheckedNodes) {
[PSCustomObject]@{
Domain = [string]$DomainBox.SelectedItem
OUName = $node.Text
OUDN = [string]$Node.Tag
}
}
$RetVal
}
ELSE {
[PSCustomObject]@{
Domain = [string]$DomainBox.SelectedItem
OUName = [string]$treeViewNav.Nodes[0].Text
OUDN = [string]$treeViewNav.Nodes[0].Tag
}
}
$Form.Close()
}
}
Function Test-DomainExists ($dom) {
Try {
[adsi]::Exists("LDAP://$dom")
}
Catch {
$False
}
}
$Script:LoadCount = 0
### End helper functions###
#Default display text if not set by argument
if ($TitleText.length -eq 0) {
if ($ShowCheckBoxes) {
$TitleText = "Select OU(s)"
}
ELSE {
$TitleText = "Select an OU"
}
}
if ($BtnText.length -eq 0) {
if ($ShowCheckBoxes) {
$BtnText = "Accept Selected OU(s)"
}
ELSE {
$BtnText = "Accept Selected OU"
}
}
if ($InstructionText.length -eq 0) {
if ($SingleDomain) {
$InstructionText = "Double click on a node to expand."
}
ELSE {
$InstructionText = "Selecting new domain will show base level. Double click on a node to expand."
}
}
if ($ShowContainers -eq $True) {
$LDAPFilter = '(|(objectClass=container)(ObjectCategory=OrganizationalUnit))'
}
ELSE {
$LDAPFilter = '(ObjectCategory=OrganizationalUnit)'
}
if ($InitialDomain.Length -eq 0) {
$InitialDomain = ([System.DirectoryServices.ActiveDirectory.domain]::GetCurrentDomain()).Name
}
If ($SingleDomain) {
$DomainList = $InitialDomain
}
ELSE {
$Forest = ([System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest())
$DomainList = ($Forest.Domains).name | Sort-Object -CaseSensitive
}
$startNum = $DomainList.IndexOf($InitialDomain)
# Import the Assemblies
Add-Type -assemblyname System.Windows.Forms
Add-Type -assemblyname System.Drawing
Add-Type -assemblyname Microsoft.VisualBasic
# Form Objects
$Form = New-Object System.Windows.Forms.Form
$DomainBox = New-Object System.Windows.Forms.ListBox
$DomainLbl = New-Object System.Windows.Forms.Label
$AcceptBtn = New-Object System.Windows.Forms.Button
$treeViewNav = New-Object System.Windows.Forms.TreeView
$InitialFormWindowState = New-Object System.Windows.Forms.FormWindowState
$StatusBar = New-Object System.Windows.Forms.StatusBar
$InstructionsLabel = New-Object System.Windows.Forms.Label
#Event Script Blocks
$DomainBox_SelectedIndexChanged = {
$CurrentDomain = $DomainBox.SelectedItem
$DomainBox.topindex = $DomainBox.SelectedIndex - 1
#check communication with selected domain
if ((Test-DomainExists $CurrentDomain) -eq $False) {
Write-Warning "$CurrentDomain does not exist or cannot be contacted."
Exit
}
$domain = [adsi]"LDAP://$CurrentDomain"
if ($Script:LoadCount -gt 0) {
$StartOUdN = ''
$InstructionsLabel.Text = $instructionText
}
Else {
$InstructionsLabel.Text = $instructionText
}
$Script:LoadCount ++
#Clear old results
$TreeviewNav.Nodes.Clear()
$newnode = New-Object System.Windows.Forms.TreeNode
$newnode.Name = $domain.Name
if (!$StartOUdN) { $StartOUdN = $domain.distinguishedName }
$newnode.Text = $StartOUdN
$newnode.Tag = $StartOUdN
$treeviewNav.Nodes.Add($newnode)
#Expand the initial tree
Invoke-command $treeviewNav_DoubleClick
}
$treeviewNav_DoubleClick = {
[System.Windows.Forms.Application]::UseWaitCursor = $true
$StatusBar.Text = "Getting list, please wait..."
if ($null -eq $treeviewNav.SelectedNode) {
#For first listing in treeview, select root
$node = $treeviewNav.Nodes[0]
}
Else {
$node = $treeviewNav.SelectedNode
}
if ($node.Nodes.Count -eq 0) {
$SearchRoot = "LDAP://$($node.Tag)"
$ADSearcher = [adsisearcher] ''
$ADSearcher.SearchRoot = $SearchRoot
$ADSearcher.PageSize = 500
$ADSearcher.SearchScope = "OneLevel"
$ADSearcher.CacheResults = $false
$ADSearcher.Filter = $LDAPFilter
$List = ($ADSearcher.FindAll()).getEnumerator().properties
foreach ($OU in $List) {
$OUName = $OU.Item("Name")
$OUDN = $OU.Item("DistinguishedName")
$newnode = New-Object System.Windows.Forms.TreeNode
$newnode.Name = $OUName
$newnode.Text = $OUName
$newnode.Tag = $OUDN
$node.Nodes.Add($newnode)
}
}
$node.Expand()
[System.Windows.Forms.Application]::UseWaitCursor = $False
$statusbar.text = ""
}
$OnLoadForm_StateCorrection =
{ #Correct the initial state of the form to prevent the .Net maximized form issue
$Form.WindowState = $InitialFormWindowState
}
$form.ClientSize = New-Object System.Drawing.Size(445, 595)
$Form.Name = "Form"
$Form.Text = $TitleText
$InstructionsLabel.Location = New-Object System.Drawing.Point(10, 15)
$InstructionsLabel.Name = "InstructionsLabel"
$InstructionsLabel.Size = New-Object System.Drawing.Size(420, 35)
$InstructionsLabel.Font = New-Object System.Drawing.Font("Microsoft Sans Serif", 8.25, 2, 3, 0)
$InstructionsLabel.Text = $InstructionText
$Form.Controls.Add($InstructionsLabel)
$DomainBox.Name = "DomainBox"
$DomainBox.FormattingEnabled = $True
$DomainBox.Font = New-Object System.Drawing.Font("Microsoft Sans Serif", 9.75, 0, 3, 0)
$DomainBox.Location = New-Object System.Drawing.Point(185, 50)
$DomainBox.Size = New-Object System.Drawing.Size(160, 55)
$DomainBox.add_SelectedIndexChanged($DomainBox_SelectedIndexChanged)
foreach ($Domain in $domainlist) {
[void] $DomainBox.Items.Add($domain)
}
$DomainBox.setSelected($startNum, $true)
$Form.Controls.Add($DomainBox)
$DomainLbl.Name = "DomainLbl"
$DomainLbl.Location = New-Object System.Drawing.Point(20, 64)
$DomainLbl.Size = New-Object System.Drawing.Size(160, 25)
$DomainLbl.Text = "Selected Domain"
$DomainLbl.TextAlign = "Middleright"
$Form.Controls.Add($DomainLbl)
$AcceptBtn.Name = "AcceptBtn"
$AcceptBtn.Location = New-Object System.Drawing.Point(125, 555)
$AcceptBtn.Size = New-Object System.Drawing.Size(170, 25)
$AcceptBtn.Text = $BtnText
$AcceptBtn.DialogResult = [System.Windows.Forms.DialogResult]::OK
$Form.Controls.Add($AcceptBtn)
$form.AcceptButton = $AcceptBtn
$treeViewNav.Name = "treeViewNav"
$treeViewNav.CheckBoxes = $ShowCheckBoxes
$treeViewNav.Location = New-Object System.Drawing.Point(45, 110)
$treeViewNav.Size = New-Object System.Drawing.Size(325, 440)
$treeViewNav.add_DoubleClick($treeViewNav_DoubleClick)
$treeViewNav.add_AfterSelect($treeViewNav_AfterSelect)
$Form.Controls.Add($treeViewNav)
$form.StartPosition = "CenterParent"
$form.Controls.Add($StatusBar)
#Save the initial state of the form
$InitialFormWindowState = $Form.WindowState
#Init the OnLoad event to correct the initial state of the form
$Form.add_Load($OnLoadForm_StateCorrection)
if ($ShowCheckBoxes) { Show-CheckBoxes }ELSE { Show-NoCheck }
} #End Function
Function Get-CustomReport {
$params = @{
BtnText = "Continue"
ShowCheckBoxes = $false
InstructionText = "Double click on an OU to expand. Users in and below selected OU will be exported."
}
$ADretval = Select-ADOU @params -InitialDomain $domain
#Extract site code from OU DN if available
$OUDN = $ADretval.OUDN
$strRegex = '\((.*?)\)'
if ([regex]::IsMatch($OUDN, $strRegex)) {
$site = [regex]::Matches($OUDN, $strRegex).groups[1].value
$site += '_'
}
Else {
$site = ''
}
if ($null -eq $ADretval) { Stop-Process $PID }
$ReportFile = "$Desktop\$site`UserExport_" + $(Get-Date).ToString("yyyyMMdd_HHmm") + '.csv'
#Splatting Get-SaveFile parameters
$params = @{
Prompt = "Select name and path for CSV report file"
Filter = "CSV Files (*.csv)|*.csv"
InitialDirectory = $desktop
DefaultFile = $reportfile
}
$reportfile = Get-SaveFile @params
if ([System.String]::IsNullOrEmpty($ReportFile)) { Exit }
Write-Host "Getting available properties from schema, please wait ...." -ForegroundColor Green
$schema = [DirectoryServices.ActiveDirectory.ActiveDirectorySchema]::GetCurrentSchema()
$userClass = $schema.FindClass('user')
$UserProps = $userClass.mandatoryProperties.name + $userClass.OptionalProperties.name + 'domain,enabled,smartCardLogonRequired'.Split(',') |
where-object { $_ -notmatch 'logonhours' } | Sort-Object
Clear-Host
#message for instructions menu`"Domain`",
$msg = @"
This script exports users with selected properties. The properties are collected from the AD Schema, and some are populated. `"Enabled`" and `"SmartCardLogonRequired`" are available without selecting UserAccountControl. The domain and canonicalname of an object are available, and dates are converted when appropriate. If you select `"memberOf`", the script will return the user's groups in the NT format. Note these less intuitive names: `"l`" is City, and `"info`" is the AD notes field. A Google search of `"ldap common user properties`" gives good results for attribute names. Nested groups add the object class and parent group to the results.`n
The next screen lets you select the attributes you want in the results. Keyboard search is first letter only. Attributes should be selected in order, using `"Move`" to choose, then `"Accept List`" to begin the export.
"@
#if you want line above without line break
$title = "Instructions"
#Ensure Inputbox is top with these three lines
$job = Start-Job $activateWindow -ArgumentList $title
[void][Microsoft.VisualBasic.Interaction]::MsgBox($msg, 'DefaultButton1, OkOnly, Information', $title)
Remove-Job $job -Force
[array]$RequestedPropList = Select-PropertiesForm $UserProps -FormTitle "Select user properties in order desired in output." -ExitOnCancel
if (($RequestedPropList.Count -ge 200) -or ($RequestedPropList.Count -eq 0)) {
Exit
}
$PropList = New-Object System.Collections.ArrayList(, ($RequestedPropList))
$SortBy = Select-PropertiesForm -FormTitle "Sort order for report. Select one or more property" -objectProps $RequestedPropList
if ($null -eq $SortBy) { Exit }
if ([regex]::IsMatch( $RequestedPropList, 'enabled|SmartCardLogonRequired')) {
if ($RequestedPropList -notcontains 'useraccountcontrol') {
[void]$PropList.Add('userAccountControl')
}
[regex]::Matches( $RequestedPropList, 'enabled|SmartCardLogonRequired') |
ForEach-Object {
[void]$PropList.Remove($_.value)
}
}
if ($RequestedPropList -notcontains 'Name') { [void]$PropList.Add('Name') }
if ($RequestedPropList -notcontains 'Class') { [void]$PropList.Add('Class') }
if ($RequestedPropList -Match 'domain|canonicalName') {
if ($RequestedPropList -notcontains 'distinguishedName') {
[void]$PropList.Add('distinguishedName')
}
[regex]::Matches( $RequestedPropList, 'domain|canonicalName') |
ForEach-Object {
[void]$PropList.Remove($_.value)
}
}
#Export users with property list
foreach ($ADSPath in $ADretval) {
Clear-Host
$domain = $ADSPath.Domain
$OU = $ADSPath.OUDN
Get-UserList -domain $domain -OU $OU -Properties $proplist
}
}
Function ToTitleCase($txt) {
$TextInfo = (Get-Culture).TextInfo
$TextInfo.ToTitleCase($txt.toLower())
}
Function Get-CertInfo ($certs) {
$Certs | ForEach-Object {
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$cert.Import([byte[]]$_)
$Issuer = $cert.Issuer
$IssuerName = $Issuer.Substring(3, $Issuer.IndexOf(", OU=") - 3)
$subject = $cert.Subject
$SName = ([regex]::Split($subject, 'CN=|\+|OID|,')[1]).trim()
$subject = ToTitleCase -txt $SName
$keyUsage = $cert.extensions.keyusages
if ($cert.EnhancedKeyUsageList.count -gt 0) {
$EnhancedUsage = ($cert.EnhancedKeyUsageList).friendlyname -join ", "
$keyUsage = "$keyUsage`: $enhancedusage"
}
$certInfo = [PsCustomObject]@{
IssuedTo = $subject
Expires = $cert.NotAfter
SN = $cert.SerialNumber
Issuer = $IssuerName
Usage = $keyUsage
}
if ($(Get-Date) -gt $cert.NotAfter ) { $Expired = 'True' }Else { $Expired = 'False' }
$CertInfo | Add-Member -NotePropertyName Expired -NotePropertyValue $Expired -PassThru
}
}
#https://petri.com/expanding-active-directory-searcher-powershell
Function Convert-ADSearchResult {
[cmdletbinding()]
Param(
[Parameter(Position = 0, Mandatory, ValueFromPipeline)]
[ValidateNotNullorEmpty()]
$SearchResult
)
Begin {
Write-Verbose "Starting $($MyInvocation.MyCommand)"
}
Process {
Write-Verbose "Processing result for $($searchResult.Path)"
#create an ordered hashtable with property names alphabetized
$props = $SearchResult.Properties.PropertyNames | Sort-Object
$objHash = [ordered]@{}
foreach ($p in $props) {
$value = $searchresult.Properties.item($p)
if ($p -match 'cert') {
$certs = ($SearchResult.GetDirectoryEntry()).$p
if ($certs.count -gt 0) {
$certInfo = Get-CertInfo $certs
[string]$CertTxt = @(($certinfo | get-member -MemberType Noteproperty).definition |
Foreach-object { [regex]::Replace($_ , '^\w*\s*', "" ) }) -join '; '
$value = $CertTxt
}
}
Else {
if ($value.count -eq 1) {
$value = $value[0]
}
}
$objHash.add($p, $value)
}
new-object psobject -property $objHash
}
End {
Write-Verbose "Ending $($MyInvocation.MyCommand)"
}
}
Function Get-UserList ($domain, $OU, $Properties) {
Write-Host "Searching $domain for selected user objects, please wait ...." -foregroundColor Green
$SearchPath = "LDAP://$domain/$OU"
$de = New-Object System.DirectoryServices.DirectoryEntry($SearchPath)
$ds = New-Object System.DirectoryServices.DirectorySearcher
$ds.SearchRoot = $de
$ds.PageSize = 500
$ds.PropertiesToLoad.AddRange(@($Properties))
$ds.Filter = "(objectCategory=user)"
$ds.SearchScope = "SubTree"
$retval = $ds.FindAll()
$iUsercount = $retval.Count
if ($iUsercount -gt 0) {
$data = $retval.GetEnumerator() |
ForEach-Object {
$i++
[int]$pct = ($i / $iUserCount) * 100
$ProgParam = @{
Activity = "Getting user information"
CurrentOperation = "$i of $iUserCount"
Status = "Please wait ..."
PercentComplete = $pct
}
Write-Progress @progParam
Convert-ADSearchResult $_
}
Write-Progress "Getting additional information (groups, manager, etc.)"
$selectProps = New-Object -TypeName System.Collections.ArrayList
$DateProps = 'accountExpires,badPasswordTime,lastLogoff,lastLogon,lastLogonTimestamp,lockoutDuration,lockOutObservationWindow,lockoutTime,maxPwdAge,minPwdAge,msDS-LastFailed,InteractiveLogonTime,msDS-LastSuccessfulInteractiveLogonTime,msDS-UserPassword,ExpiryTimeComputed,pwdLastSet'.Split(',')
foreach ($field in $RequestedPropList) {
switch ($field) {
'domain' {
$selectProps.Add(@{
Name = 'Domain'
Expression = [Scriptblock]::Create("Get-DomainFromDNString -stradspath (`$_).distinguishedName")
}) | out-null
break
}
'Enabled' {
$selectProps.Add(@{
Name = 'Enabled'
Expression = [Scriptblock]::Create("![bool]((`$_.userAccountControl -band 2) -eq 2)"
)
}) | out-null
break
}
'SmartCardLogonRequired' {
$selectProps.add(@{
Name = 'SmartCardLogonRequired'
Expression = [Scriptblock]::Create("[bool]((`$_.userAccountControl -band 262144) -eq 262144)")
}) | out-null
break
}
'canonicalName' {
$selectProps.add(@{
Name = 'CanonicalName'
Expression = [Scriptblock]::Create("Convert-DNtoCanonical -adspath `$_.distinguishedName")
}) | Out-Null
break
}
'UserAccountControl' {
$selectProps.Add(@{
Name = 'UserAccountControl'
Expression = [Scriptblock]::Create("'[' + `$_.userAccountControl + '] ' + [UserAccountControl]`$_.userAccountControl")
}) | out-null
break
}
'MsDS-SupportedEncryptionTypes' {
$selectProps.Add(@{
Name = 'SupportedEncryptionTypes'
Expression = [Scriptblock]::Create("Get-SupportedEncryption `$_.`'MsDS-SupportedEncryptionTypes`'")
}) | out-null
break
}
'MemberOf' {
$selectProps.Add(@{
Name = 'Groups'
Expression = [Scriptblock]::Create("Get-GroupNames `$_.memberOf")
}) | out-null
break
}
'Manager' {
$selectProps.Add(@{
Name = 'ManagerUPN'
Expression = [Scriptblock]::Create("Get-ManagerUPN `$_.manager")
}) | out-null
break
}
Default {
if ($field -in $DateProps) {
$selectProps.add(@{
Name = "$field "
Expression = [Scriptblock]::Create("[datetime]::FromFileTime(`$_.'$field')")
}) | Out-Null
}
Else {
$field = $field.ToString()
$selectProps.add($field) | out-null
}
}
}
}
}
ELSE {
Write-warning "Search failed for `n$searchroot"
Pause
}
$data | Select-Object -property $SelectProps |
Sort-Object -Property $SortBy | Select-Object * -Unique |
export-csv $ReportFile -NoTypeInformation -Append
Write-Progress -Completed "Done"
Write-Host "Done." -foregroundColor Green
Invoke-Item $ReportFile
}
# Script Starts
$ForestName = [system.directoryservices.activedirectory.forest]::GetCurrentForest().Name.ToString()
#Get my user account for initial search base
$SearchRoot = "GC://$ForestName"
$filter = "(&(objectCategory=User)(objectClass=Person)(samAccountName=$env:UserName))"
$ADSearcher = [adsisearcher] ''
$ADSearcher.SearchRoot = $SearchRoot
$ADSearcher.PageSize = 500
$ADSearcher.SearchScope = "SubTree"
$ADSearcher.CacheResults = $false
$ADSearcher.Filter = $filter
$domain = ($env:USERDNSDOMAIN).ToString().ToLower()
Get-CustomReport