# Fix-Passwords.ps1 # Resets password hashes for both SuperAdmin users in the Azure SQL database. # Runs directly via sqlcmd to avoid copy-paste corruption of Base64 characters. # # Usage (interactive - will prompt for connection details): # .\Fix-Passwords.ps1 # # Usage (non-interactive): # .\Fix-Passwords.ps1 -Server "yourserver.database.windows.net" -Database "PowderCoatingDb" -User "sqladmin" -Password "yourpassword" param( [string]$Server, [string]$Database = "PowderCoatingapp", [string]$User, [string]$Password ) function New-IdentityPasswordHash { param([string]$Password) $salt = [byte[]]::new(16) $rng = [System.Security.Cryptography.RandomNumberGenerator]::Create() $rng.GetBytes($salt) $rng.Dispose() $pbkdf2 = [System.Security.Cryptography.Rfc2898DeriveBytes]::new( $Password, $salt, 100000, [System.Security.Cryptography.HashAlgorithmName]::SHA256) $subkey = $pbkdf2.GetBytes(32) $pbkdf2.Dispose() # ASP.NET Core Identity v3 format $buf = [byte[]]::new(61) $buf[0] = 0x01 $buf[1] = 0x00; $buf[2] = 0x00; $buf[3] = 0x00; $buf[4] = 0x01 # PRF = HMACSHA256 $buf[5] = 0x00; $buf[6] = 0x01; $buf[7] = 0x86; $buf[8] = 0xA0 # 100000 iterations $buf[9] = 0x00; $buf[10] = 0x00; $buf[11] = 0x00; $buf[12] = 0x10 # salt length = 16 [Array]::Copy($salt, 0, $buf, 13, 16) [Array]::Copy($subkey, 0, $buf, 29, 32) return [Convert]::ToBase64String($buf) } # ---- Prompt for connection details if not provided ---------------------------- if (-not $Server) { $Server = Read-Host "Azure SQL Server (e.g. yourserver.database.windows.net)" } if (-not $User) { $User = Read-Host "SQL Username" } if (-not $Password) { $securePass = Read-Host "SQL Password" -AsSecureString $Password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto( [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($securePass)) } # ---- Check sqlcmd is available ----------------------------------------------- $sqlcmd = Get-Command sqlcmd -ErrorAction SilentlyContinue if (-not $sqlcmd) { Write-Host "" Write-Host "ERROR: sqlcmd not found on PATH." -ForegroundColor Red Write-Host "Install it from: https://learn.microsoft.com/en-us/sql/tools/sqlcmd/sqlcmd-utility" -ForegroundColor Yellow Write-Host "" Write-Host "Alternatively, copy fix_passwords.sql to the Azure Portal Query Editor manually." -ForegroundColor Yellow Write-Host "(Be careful that + and / characters are not changed during copy-paste.)" -ForegroundColor Yellow $useSqlCmd = $false } else { $useSqlCmd = $true } # ---- Generate hashes --------------------------------------------------------- Write-Host "" Write-Host "Generating password hashes..." -ForegroundColor Cyan $hash1 = New-IdentityPasswordHash "SuperAdmin123!" $hash2 = New-IdentityPasswordHash "CompanyAdmin123!" $stamp1 = [Guid]::NewGuid().ToString() $stamp2 = [Guid]::NewGuid().ToString() Write-Host "Done." -ForegroundColor Cyan # ---- Build SQL --------------------------------------------------------------- $sql = @" SET QUOTED_IDENTIFIER ON; SET NOCOUNT ON; -- Reset password hash for superadmin@powdercoating.com / SuperAdmin123! UPDATE AspNetUsers SET PasswordHash = '$hash1', SecurityStamp = '$stamp1' WHERE NormalizedEmail = 'SUPERADMIN@POWDERCOATING.COM'; PRINT 'Updated: superadmin@powdercoating.com'; -- Reset password hash for admin@demo.com / CompanyAdmin123! UPDATE AspNetUsers SET PasswordHash = '$hash2', SecurityStamp = '$stamp2' WHERE NormalizedEmail = 'ADMIN@DEMO.COM'; PRINT 'Updated: admin@demo.com'; -- Verify both users exist and have non-null hashes SELECT Email, LEN(PasswordHash) AS HashLength, IsActive FROM AspNetUsers WHERE NormalizedEmail IN ('SUPERADMIN@POWDERCOATING.COM', 'ADMIN@DEMO.COM'); "@ # ---- Write SQL file (always, as backup) -------------------------------------- $sqlFile = Join-Path $PSScriptRoot "fix_passwords.sql" $sql | Set-Content -Path $sqlFile -Encoding UTF8 Write-Host "SQL written to: $sqlFile" -ForegroundColor Gray # ---- Execute via sqlcmd (avoids copy-paste corruption) ----------------------- if ($useSqlCmd) { Write-Host "" Write-Host "Running via sqlcmd (no copy-paste, Base64 characters preserved)..." -ForegroundColor Cyan $result = & sqlcmd ` -S $Server ` -d $Database ` -U $User ` -P $Password ` -Q $sql ` -l 30 2>&1 Write-Host "" $result | ForEach-Object { Write-Host $_ } if ($LASTEXITCODE -eq 0) { Write-Host "" Write-Host "SUCCESS. Try logging in now:" -ForegroundColor Green Write-Host " superadmin@powdercoating.com / SuperAdmin123!" -ForegroundColor White Write-Host " admin@demo.com / CompanyAdmin123!" -ForegroundColor White } else { Write-Host "" Write-Host "sqlcmd returned exit code $LASTEXITCODE. Check the output above." -ForegroundColor Red Write-Host "You can also run fix_passwords.sql manually in the Azure Portal Query Editor." -ForegroundColor Yellow } } else { Write-Host "" Write-Host "Run fix_passwords.sql in Azure Portal > SQL Database > Query Editor." -ForegroundColor Yellow Write-Host "TIP: If + or / appear as spaces after pasting, the hash will be corrupted again." -ForegroundColor Yellow Write-Host " Use sqlcmd instead to be safe." -ForegroundColor Yellow }