How to Change the Windows Short Date Format with PowerShell

Stop wasting time on manual registry hacks. This PowerShell script synchronizes short date formats across current, default, and system-wide profiles.

Sometimes, you need every user on a machine to follow the same standard—for example, forcing dd-MM-yy instead of the default M/d/yyyy. In an enterprise environment, relying on users to manually update their regional settings is a recipe for inconsistent logs and reports.

Normally, this requires mounting the offline NTUSER.dat or manually hunting down registry keys for every active profile. The script below handles the heavy lifting by targeting the current user, the system default, and—critically—the Default User profile so that any new user who logs in gets the correct format automatically.

The Technical Breakdown

This isn't just a simple Set-ItemProperty script. We’ve built in logic to ensure the changes stick without a reboot:

  1. SID Detection: It finds the Security Identifier (SID) of the currently logged-in user by querying the owner of explorer.exe.
  2. The Default Profile: It updates HKEY_USERS.DEFAULT\Control Panel\International so you don't have to fix future profiles.
  3. The System Default: It updates HKEY_LOCAL_MACHINE\Control Panel\International for machine-wide consistency.
  4. The "Live" Broadcast: It uses a C# snippet via Add-Type to send a WM_SETTINGCHANGE message. This notifies running applications that the "International" settings have changed, avoiding a full logoff/logon cycle for many apps.
  5. Robustness: The entire operation runs as a background job with a 5-minute timeout to prevent hanging on locked registry keys.

The Automation Script

$targetFormat = "dd-MM-yy"
$timeoutSeconds = 300   # 5 minutes

$job = Start-Job -ArgumentList $targetFormat -ScriptBlock {
    param($targetFormat)

    function Set-DateFormat {
        param($RegPath, $HiveTag)
        try {
            if (-not (Test-Path $RegPath)) {
                Write-Output "[$HiveTag] Registry path not found: $RegPath"
                return
            }
            $cur = (Get-ItemProperty -Path $RegPath -Name sShortDate -ErrorAction SilentlyContinue).sShortDate
            if (-not $cur) {
                Write-Output "[$HiveTag] Unable to read current format."
                return
            }
            Write-Output "[$HiveTag] Current format: $cur"
            if ($cur -eq $targetFormat) {
                Write-Output "[$HiveTag] Already set to $targetFormat."
            } else {
                Set-ItemProperty -Path $RegPath -Name sShortDate -Value $targetFormat -ErrorAction Stop
                Write-Output "[$HiveTag] Updated to $targetFormat."
            }
        } catch {
            Write-Output "[$HiveTag] Error: $_"
        }
    }

    Write-Output "Step 1: Detecting logged-in user SID..."
    $explorer = Get-Process explorer -ErrorAction SilentlyContinue | Select-Object -First 1
    if ($explorer) {
        try {
            $proc = Get-CimInstance Win32_Process -Filter "ProcessId=$($explorer.Id)"
            $sid = ($proc | Invoke-CimMethod -MethodName GetOwnerSid).Sid
            if ($sid) {
                Write-Output "Logged-in user SID: $sid"
                $userReg = "Registry::HKEY_USERS$sid\Control Panel\International"
                Set-DateFormat -RegPath $userReg -HiveTag "Logged-in User"
            } else {
                Write-Output "Failed to retrieve SID from explorer.exe"
            }
        } catch {
            Write-Output "Error detecting SID: $_"
        }
    } else {
        Write-Output "No explorer.exe found; no interactive user detected."
    }

    Write-Output "Step 2: Updating Default User (new profiles)..."
    Set-DateFormat -RegPath "Registry::HKEY_USERS\.DEFAULT\Control Panel\International" -HiveTag "Default User"

    Write-Output "Step 3: Updating HKLM system default..."
    Set-DateFormat -RegPath "Registry::HKEY_LOCAL_MACHINE\Control Panel\International" -HiveTag "System Default"

    Write-Output "Step 4: Broadcasting settings change..."
    try {
        $sig = @"
using System;
using System.Runtime.InteropServices;
public static class NativeMethods {
  [DllImport("user32.dll", SetLastError=true)]
  public static extern IntPtr SendMessageTimeout(IntPtr hWnd, uint Msg, UIntPtr wParam, string lParam, uint fuFlags, uint uTimeout, out UIntPtr lpdwResult);
}
"@
        Add-Type -TypeDefinition $sig -ErrorAction SilentlyContinue
        $HWND_BROADCAST = [intptr]0xffff
        $WM_SETTINGCHANGE = 0x1A
        [UIntPtr]$zero = [UIntPtr]::Zero
        [UIntPtr]$out = [UIntPtr]::Zero
        [NativeMethods]::SendMessageTimeout($HWND_BROADCAST, $WM_SETTINGCHANGE, $zero, "International", 2, 5000, [ref]$out) | Out-Null
        Write-Output "Broadcast attempted."
    } catch {
        Write-Output "Broadcast failed: $_"
    }

    Write-Output "Job finished."
}

$finished = Wait-Job -Job $job -Timeout $timeoutSeconds

if (-not $finished) {
    Write-Output "Timeout ($timeoutSeconds seconds) reached. Stopping job..."
    Stop-Job -Job $job
    $jobState = (Get-Job -Id $job.Id).State
    Write-Output "Job state after stop: $jobState"
} else {
    Write-Output "Job completed within timeout."
}

Receive-Job -Job $job -Keep | ForEach-Object { Write-Output $_ }
Remove-Job -Job $job -ErrorAction SilentlyContinue

Final Verdict

If you are managing a fleet of jump servers or kiosks where date format consistency is non-negotiable, stop clicking through the UI. This script ensures that HKEY_LOCAL_MACHINE, the .DEFAULT hive, and the Active User are all in sync.

Just run this as Administrator, and you're done. No reboots (though some apps may still require it), no user confusion.