Checking Estonian ID code correctness in PowerShell

This is based on an implementation in another language I found many years ago on Google, I’ve forgotten the details or the exact source.
As usual, it’s not the most elegant version but works just fine and hasn’t been modified in years. For formal validation algorithm, use Google. I haven’t seen any official public document for it but there are a few implementation examples out there (PHP, Delphi, C#, JS etc.).

I originally used it for automatically loading ID Card certificates to Active Directory for SmartCard login. I’ll build up to releasing that by going over various pieces to making it work.

Remarks:

  • Wrap function call in Try-Catch and If. Function parameter validation returns error but actual ID code validation returns true-false. I know it’s ugly but it’s good enough for me.
  • It really only checks if string contains exactly 11 numbers and checksum is correct. There is no guarantee that a person with that code actually exists.
Function Validate-Isikukood {
	param(
		[parameter(Mandatory=$true)]
		[ValidatePattern("^\d{11}$")]
		[string]$Isikukood
	)
	[char[]]$IsikukoodArray = $Isikukood.ToCharArray()
	$IDCheck1 = [convert]::ToInt32($IsikukoodArray[0],10) * 1 + [convert]::ToInt32($IsikukoodArray[1],10) * 2 + [convert]::ToInt32($IsikukoodArray[2],10) * 3 + [convert]::ToInt32($IsikukoodArray[3],10) * 4 + [convert]::ToInt32($IsikukoodArray[4],10) * 5 + [convert]::ToInt32($IsikukoodArray[5],10) * 6 + [convert]::ToInt32($IsikukoodArray[6],10) * 7 + [convert]::ToInt32($IsikukoodArray[7],10) * 8 + [convert]::ToInt32($IsikukoodArray[8],10) * 9 + [convert]::ToInt32($IsikukoodArray[9],10) * 1
	$IDCheckSum = $IDCheck1 % 11
	If ($IDCheckSum -eq 10) {
		$IDCheck2 = [convert]::ToInt32($IsikukoodArray[0],10) * 3 + [convert]::ToInt32($IsikukoodArray[1],10) * 4 + [convert]::ToInt32($IsikukoodArray[2],10) * 5 + [convert]::ToInt32($IsikukoodArray[3],10) * 6 + [convert]::ToInt32($IsikukoodArray[4],10) * 7 + [convert]::ToInt32($IsikukoodArray[5],10) * 8 + [convert]::ToInt32($IsikukoodArray[6],10) * 9 + [convert]::ToInt32($IsikukoodArray[7],10) * 1 + [convert]::ToInt32($IsikukoodArray[8],10) * 2 + [convert]::ToInt32($IsikukoodArray[9],10) * 3
		$IDCheckSum = $IDCheck2 % 11
		If (($IDCheckSum -eq 10) -and ([convert]::ToInt32($IsikukoodArray[10],10) -eq 0)) {
			Return $True
		} ElseIf (($IDCheckSum -ne 10) -and ([convert]::ToInt32($IsikukoodArray[10],10) -eq $IDCheckSum)) {
			Return $True
		} Else {
			Return $False
		}
	} ElseIf (($IDCheckSum -ne 10) -and ([convert]::ToInt32($IsikukoodArray[10],10) -eq $IDCheckSum)) {
		Return $True
	} Else {
		Return $False
	}
}

Calculating size of user’s mailbox and any delegated mailboxes

Outlook by default limits OST to 50GB (modern versions) but some users may have tons of delegated mailboxes and run into this limit. This script retrieves users that have more than 50GB of delegated and personal mailboxes visible. You might not want to increase OST limit for everyone…

Possible use case is situation where you have delegated several large mailboxes to multiple users. As tickets start coming in as mailboxes grow, you want to proactively find out problematic users.

This really becomes an issue when you delegate mailboxes to groups. I’ll post script to update msExchDelegateListBL for group memberships in a few days as Exchange doesn’t do that automatically. TL;DR: If you delegate mailbox to group, it doesn’t get autoloaded by Outlook. I have a script to remediate that.

Remarks:

  • This is a slow and ugly one-off. But as I only needed it once, it just works. As always, read the disclaimer on the left.
  • You need Exchange Management Tools installed on your PC. It doesn’t work with remote management PowerShell session as you don’t have proper data types loaded. Install management tools on your PC and run Exchange Management Shell.
  • This script looks up only admin-delegated mailboxes. Any folders or mailboxes or public folders shared and loaded by users themselves are not included. This is server-side view only.
$userlist = get-aduser -Filter *
foreach ($user in $userlist) {
	$usermailbox = get-mailbox $user.distinguishedname 2>$null
	If ($usermailbox) {
		$DelegationList = (get-aduser -Identity $user.distinguishedname -Properties msExchDelegateListBL).msExchDelegateListBL
		If ($DelegationList) {
			$usermailboxsize = (Get-mailboxstatistics -identity $usermailbox | select @{label=”TotalSizeBytes”;expression={$_.TotalItemSize.Value.ToBytes()}}).TotalSizeBytes
			$SharedSize = ($DelegationList | %{get-mailbox -Identity $_ | Get-MailboxStatistics | select displayname,@{label=”TotalSizeBytes”;expression={$_.TotalItemSize.Value.ToBytes()}},totalitemsize} | measure -sum totalsizebytes).sum
			$TotalVisibleSize = ( ($usermailboxsize + $SharedSize) / 1GB)
			If ($TotalVisibleSize -gt 50) {
				Write-Host $user.Name
				Write-Host $TotalVisibleSize
			}
		}
	}
}

Powershell arrays are passed by reference, unlike basic variables‏

PowerShell is great in many ways yet very unintuitive in others.

Consider following example:

$a = 0
$b = $a
$b = 1
$a #0
$b #1

All seems good and logical? Now introduce arrays:

$a=@(1)
$a #1
$b=$a
$b[0]=2
$a #2!
$b #2

What? How did $a change? Surely this is an artifact of direct modification or something. Let’s try passing array to a function.

$a=@(1)
function b {param($c);$d=$c;$d[0]=2;$d}
$a #1
b $a #2
$a #2!

Now that’s annoying if you’re passing the same array around in a script. No level of scoping or any other tinkering will fix that. A bit of MSDN and StackOverflow reveals that arrays are always, and I mean always, passed by reference, something inherited from .Net. There are a few not-so-pretty workarounds.

use .Clone() method. Caveat is that it only works one level. So it you use multidimensional arrays, you’re out of luck. Example:

$a=@(1,@(1))
function b {param($c);$d=$c.Clone();$d[0]=2;$d[1][0]=2;$d}
$a #1,1
b $a #2,2
$a #1,2!

As you can see, first level of array works fine but second does not.

Serialize-deserialize array. That’s a really ugly workaround but it’s guaranteed to work. Take a look here. I haven’t tested it because cloning worked for my needs but I have a feeling that it is much slower. That may or not be an issue depending on your requirements. Might be a good idea to wrap it in a function for easy use.

Wishlist: runtime flag or global variable to pass arrays by value.