[{"data":1,"prerenderedAt":2080},["ShallowReactive",2],{"post:\u002F2025\u002F04\u002F24\u002Fpowershell-azure-rotate-storage-account-access-keys-on-a-schedule\u002F":3},{"post":4,"newer":2028,"older":2040,"related":2050,"series":2078},{"id":5,"title":6,"body":7,"canonical":2010,"categories":2011,"date":2014,"description":2015,"extension":2016,"featured":2017,"hero":2018,"image":2018,"meta":2019,"navigation":432,"path":2020,"readingTime":474,"seo":2021,"series":2018,"seriesOrder":2018,"sites":2022,"source":2018,"stem":2023,"tags":2024,"updated":2018,"url":2026,"__hash__":2027},"blog\u002Fblog\u002F2025\u002F04\u002F24\u002Fpowershell-azure-rotate-storage-account-access-keys-on-a-schedule.md","PowerShell: Azure – Rotate Storage Account Access Keys on a Schedule",{"type":8,"value":9,"toc":2002},"minimark",[10,19,30,35,88,92,223,235,239,242,298,306,309,341,347,354,390,396,545,556,560,1846,1850,1931,1935,1998],[11,12,13,14,18],"p",{},"Every Azure Storage account ships with two 512-bit access keys so you can rotate one while the other keeps serving traffic. In practice almost nobody rotates them until an audit finds a key that has been live since the account was created. This script automates the rotation: it reads the account's ",[15,16,17],"code",{},"KeyCreationTime"," to find which of the two keys is older, regenerates that one, and pushes the new connection string into Key Vault as a new secret version so consumers pick it up without a manual copy-paste.",[11,20,21,22,25,26,29],{},"Before you schedule it, one caveat Microsoft now puts at the top of its own rotation tutorial: for Blob, Queue and Table data the recommended fix is not better key rotation but no keys at all. Authorize clients with Microsoft Entra ID and managed identities, then set ",[15,23,24],{},"AllowSharedKeyAccess"," to ",[15,27,28],{},"$false"," on the account. Use this script for the workloads that still genuinely need a key or connection string, and treat every account it touches as a migration candidate.",[31,32,34],"h2",{"id":33},"requirements","Requirements",[36,37,38,54,69,76,79],"ul",{},[39,40,41,42,45,46,49,50,53],"li",{},"PowerShell 7.x (Windows PowerShell 5.1 also works) with the ",[15,43,44],{},"Az.Accounts",", ",[15,47,48],{},"Az.Storage"," and ",[15,51,52],{},"Az.KeyVault"," modules.",[39,55,56,57,61,62,49,65,68],{},"On the storage account: Reader plus ",[58,59,60],"strong",{},"Storage Account Key Operator Service Role",", or Contributor. The rotation needs ",[15,63,64],{},"Microsoft.Storage\u002FstorageAccounts\u002Flistkeys\u002Faction",[15,66,67],{},"Microsoft.Storage\u002FstorageAccounts\u002Fregeneratekey\u002Faction","; Owner, Contributor and Storage Account Key Operator Service Role include both.",[39,70,71,72,75],{},"On the vault: ",[58,73,74],{},"Key Vault Secrets Officer"," (RBAC permission model) or an access policy with secret Set rights.",[39,77,78],{},"For unattended runs: an Azure Automation account with a system-assigned managed identity that holds the roles above.",[39,80,81,82,84,85,87],{},"The account must still allow Shared Key authorization. If ",[15,83,24],{}," is ",[15,86,28],{},", the keys are not accepted anyway and the script stops without rotating.",[31,89,91],{"id":90},"parameters","Parameters",[93,94,95,114],"table",{},[96,97,98],"thead",{},[99,100,101,105,108,111],"tr",{},[102,103,104],"th",{},"Name",[102,106,107],{},"Type",[102,109,110],{},"Required",[102,112,113],{},"Description",[115,116,117,134,148,162,181,204],"tbody",{},[99,118,119,125,128,131],{},[120,121,122],"td",{},[15,123,124],{},"ResourceGroupName",[120,126,127],{},"String",[120,129,130],{},"Yes",[120,132,133],{},"Resource group that contains the storage account.",[99,135,136,141,143,145],{},[120,137,138],{},[15,139,140],{},"StorageAccountName",[120,142,127],{},[120,144,130],{},[120,146,147],{},"Name of the storage account whose key is being rotated.",[99,149,150,155,157,159],{},[120,151,152],{},[15,153,154],{},"KeyVaultName",[120,156,127],{},[120,158,130],{},[120,160,161],{},"Key Vault that receives the new connection string.",[99,163,164,169,171,174],{},[120,165,166],{},[15,167,168],{},"SecretName",[120,170,127],{},[120,172,173],{},"No",[120,175,176,177,180],{},"Secret name in the vault. Defaults to ",[15,178,179],{},"\u003CStorageAccountName>-connection-string",".",[99,182,183,188,191,193],{},[120,184,185],{},[15,186,187],{},"SecretValidityDays",[120,189,190],{},"Int",[120,192,173],{},[120,194,195,196,199,200,203],{},"Sets the secret version's expiry this many days out. Default ",[15,197,198],{},"60",". Use ",[15,201,202],{},"0"," for no expiry.",[99,205,206,211,214,216],{},[120,207,208],{},[15,209,210],{},"UseManagedIdentity",[120,212,213],{},"Switch",[120,215,173],{},[120,217,218,219,222],{},"Signs in with ",[15,220,221],{},"Connect-AzAccount -Identity"," first (for Automation runbooks).",[11,224,225,49,228,231,232,180],{},[15,226,227],{},"-WhatIf",[15,229,230],{},"-Confirm"," also work, because the script declares ",[15,233,234],{},"SupportsShouldProcess",[31,236,238],{"id":237},"usage","Usage",[11,240,241],{},"Preview which key would be rotated, without regenerating anything or writing to the vault:",[243,244,249],"pre",{"className":245,"code":246,"language":247,"meta":248,"style":248},"language-powershell shiki shiki-themes github-dark",".\\Rotate-StorageAccountKey.ps1 -ResourceGroupName \"rg-prod-data\" -StorageAccountName \"stprodreports01\" -KeyVaultName \"kv-prod-secrets\" -WhatIf\n","powershell","",[15,250,251],{"__ignoreMap":248},[252,253,256,260,264,267,269,272,276,279,282,285,287,290,293,295],"span",{"class":254,"line":255},"line",1,[252,257,259],{"class":258},"s95oV",".\\Rotate",[252,261,263],{"class":262},"snl16","-",[252,265,266],{"class":258},"StorageAccountKey.ps1 ",[252,268,263],{"class":262},[252,270,271],{"class":258},"ResourceGroupName ",[252,273,275],{"class":274},"sU2Wk","\"rg-prod-data\"",[252,277,278],{"class":262}," -",[252,280,281],{"class":258},"StorageAccountName ",[252,283,284],{"class":274},"\"stprodreports01\"",[252,286,278],{"class":262},[252,288,289],{"class":258},"KeyVaultName ",[252,291,292],{"class":274},"\"kv-prod-secrets\"",[252,294,278],{"class":262},[252,296,297],{"class":258},"WhatIf\n",[243,299,304],{"className":300,"code":302,"language":303,"meta":248},[301],"language-text","key1 created 2025-01-14T09:02:11Z, key2 created 2025-02-27T09:01:48Z. Older key: key1.\nWhat if: Performing the operation \"Regenerate key1 and write secret stprodreports01-connection-string\" on target \"stprodreports01\".\n","text",[15,305,302],{"__ignoreMap":248},[11,307,308],{},"Rotate for real and show the result object:",[243,310,312],{"className":245,"code":311,"language":247,"meta":248,"style":248},".\\Rotate-StorageAccountKey.ps1 -ResourceGroupName \"rg-prod-data\" -StorageAccountName \"stprodreports01\" -KeyVaultName \"kv-prod-secrets\"\n",[15,313,314],{"__ignoreMap":248},[252,315,316,318,320,322,324,326,328,330,332,334,336,338],{"class":254,"line":255},[252,317,259],{"class":258},[252,319,263],{"class":262},[252,321,266],{"class":258},[252,323,263],{"class":262},[252,325,271],{"class":258},[252,327,275],{"class":274},[252,329,278],{"class":262},[252,331,281],{"class":258},[252,333,284],{"class":274},[252,335,278],{"class":262},[252,337,289],{"class":258},[252,339,340],{"class":274},"\"kv-prod-secrets\"\n",[243,342,345],{"className":343,"code":344,"language":303,"meta":248},[301],"key1 created 2025-01-14T09:02:11Z, key2 created 2025-02-27T09:01:48Z. Older key: key1.\n\nStorageAccount : stprodreports01\nRotatedKey     : key1\nSecretName     : stprodreports01-connection-string\nSecretVersion  : 3f1c9a0e5b2d4c7e8a6f0b1d2c3e4f5a\nSecretExpires  : 2025-06-23 14:00:07Z\n",[15,346,344],{"__ignoreMap":248},[11,348,349,350,353],{},"In an Azure Automation runbook, import the script as a PowerShell runbook, link it to a schedule with the parameters filled in, and pass ",[15,351,352],{},"-UseManagedIdentity"," so it signs in as the Automation account:",[243,355,357],{"className":245,"code":356,"language":247,"meta":248,"style":248},".\\Rotate-StorageAccountKey.ps1 -ResourceGroupName \"rg-prod-data\" -StorageAccountName \"stprodreports01\" -KeyVaultName \"kv-prod-secrets\" -UseManagedIdentity\n",[15,358,359],{"__ignoreMap":248},[252,360,361,363,365,367,369,371,373,375,377,379,381,383,385,387],{"class":254,"line":255},[252,362,259],{"class":258},[252,364,263],{"class":262},[252,366,266],{"class":258},[252,368,263],{"class":262},[252,370,271],{"class":258},[252,372,275],{"class":274},[252,374,278],{"class":262},[252,376,281],{"class":258},[252,378,284],{"class":274},[252,380,278],{"class":262},[252,382,289],{"class":258},[252,384,292],{"class":274},[252,386,278],{"class":262},[252,388,389],{"class":258},"UseManagedIdentity\n",[11,391,392,393,395],{},"Once both keys have been rotated at least once, set a key expiration policy so the portal and Azure Policy can flag accounts that fall behind. The ",[15,394,17],{}," check mirrors Microsoft's own example, because the policy cannot be set while either value is null:",[243,397,399],{"className":245,"code":398,"language":247,"meta":248,"style":248},"$account = Get-AzStorageAccount -ResourceGroupName \"rg-prod-data\" -Name \"stprodreports01\"\n\nif ($null -eq $account.KeyCreationTime.Key1 -or $null -eq $account.KeyCreationTime.Key2) {\n    Write-Warning \"Rotate both keys once before setting a key expiration policy.\"\n} else {\n    Set-AzStorageAccount -ResourceGroupName \"rg-prod-data\" -Name \"stprodreports01\" -KeyExpirationPeriodInDay 60 | Out-Null\n    (Get-AzStorageAccount -ResourceGroupName \"rg-prod-data\" -Name \"stprodreports01\").KeyPolicy\n}\n",[15,400,401,427,434,463,472,484,515,539],{"__ignoreMap":248},[252,402,403,406,409,413,415,417,419,421,424],{"class":254,"line":255},[252,404,405],{"class":258},"$account ",[252,407,408],{"class":262},"=",[252,410,412],{"class":411},"sDLfK"," Get-AzStorageAccount",[252,414,278],{"class":262},[252,416,271],{"class":258},[252,418,275],{"class":274},[252,420,278],{"class":262},[252,422,423],{"class":258},"Name ",[252,425,426],{"class":274},"\"stprodreports01\"\n",[252,428,430],{"class":254,"line":429},2,[252,431,433],{"emptyLinePlaceholder":432},true,"\n",[252,435,437,440,443,446,449,452,455,458,460],{"class":254,"line":436},3,[252,438,439],{"class":262},"if",[252,441,442],{"class":258}," (",[252,444,445],{"class":411},"$null",[252,447,448],{"class":262}," -eq",[252,450,451],{"class":258}," $account.KeyCreationTime.Key1 ",[252,453,454],{"class":262},"-or",[252,456,457],{"class":411}," $null",[252,459,448],{"class":262},[252,461,462],{"class":258}," $account.KeyCreationTime.Key2) {\n",[252,464,466,469],{"class":254,"line":465},4,[252,467,468],{"class":411},"    Write-Warning",[252,470,471],{"class":274}," \"Rotate both keys once before setting a key expiration policy.\"\n",[252,473,475,478,481],{"class":254,"line":474},5,[252,476,477],{"class":258},"} ",[252,479,480],{"class":262},"else",[252,482,483],{"class":258}," {\n",[252,485,487,490,492,494,496,498,500,502,504,507,509,512],{"class":254,"line":486},6,[252,488,489],{"class":411},"    Set-AzStorageAccount",[252,491,278],{"class":262},[252,493,271],{"class":258},[252,495,275],{"class":274},[252,497,278],{"class":262},[252,499,423],{"class":258},[252,501,284],{"class":274},[252,503,278],{"class":262},[252,505,506],{"class":258},"KeyExpirationPeriodInDay ",[252,508,198],{"class":411},[252,510,511],{"class":262}," |",[252,513,514],{"class":411}," Out-Null\n",[252,516,518,521,524,526,528,530,532,534,536],{"class":254,"line":517},7,[252,519,520],{"class":258},"    (",[252,522,523],{"class":411},"Get-AzStorageAccount",[252,525,278],{"class":262},[252,527,271],{"class":258},[252,529,275],{"class":274},[252,531,278],{"class":262},[252,533,423],{"class":258},[252,535,284],{"class":274},[252,537,538],{"class":258},").KeyPolicy\n",[252,540,542],{"class":254,"line":541},8,[252,543,544],{"class":258},"}\n",[11,546,547,548,551,552,555],{},"With one run a month, each key is regenerated every other month, so a 60-day ",[15,549,550],{},"KeyExpirationPeriodInDay"," matches the schedule. Assign the built-in policy ",[58,553,554],{},"Storage account keys should not be expired"," at subscription scope to report accounts that miss it.",[31,557,559],{"id":558},"script","Script",[243,561,563],{"className":245,"code":562,"language":247,"meta":248,"style":248},"\u003C#\n.SYNOPSIS\n    Regenerates the older of a storage account's two access keys and writes the new connection string to Key Vault.\n.DESCRIPTION\n    Reads KeyCreationTime from the storage account to find which of key1 and key2 was created or\n    rotated longer ago (key1 if the timestamps are missing), regenerates that key with\n    New-AzStorageAccountKey, reads the new value back with Get-AzStorageAccountKey, builds a\n    connection string using the endpoint suffix of the current Azure environment, and stores it as a\n    new version of the named Key Vault secret with Set-AzKeyVaultSecret. The secret version gets an\n    expiry date, a content type and a CredentialId tag naming the key it holds. Stops without\n    rotating if the account has Shared Key authorization disabled. Supports -WhatIf and -Confirm.\n.PARAMETER ResourceGroupName\n    Resource group that contains the storage account.\n.PARAMETER StorageAccountName\n    Name of the storage account whose key is being rotated.\n.PARAMETER KeyVaultName\n    Key Vault that receives the new connection string.\n.PARAMETER SecretName\n    Secret name in the vault. Defaults to \"\u003CStorageAccountName>-connection-string\".\n.PARAMETER SecretValidityDays\n    Sets the secret version's expiry this many days out. Default 60. Use 0 for no expiry.\n.PARAMETER UseManagedIdentity\n    Signs in with Connect-AzAccount -Identity first (for Automation runbooks).\n.EXAMPLE\n    .\\Rotate-StorageAccountKey.ps1 -ResourceGroupName \"rg-prod-data\" -StorageAccountName \"stprodreports01\" -KeyVaultName \"kv-prod-secrets\" -WhatIf\n.NOTES\n    Author  : Thomas Lasswell (https:\u002F\u002Fwww.techcolumnist.com)\n    Version : 1.0 (2025-04-24)\n    Requires: Az.Accounts, Az.Storage, Az.KeyVault\n#>\n[CmdletBinding(SupportsShouldProcess = $true)]\nparam(\n    [Parameter(Mandatory = $true)]\n    [string]$ResourceGroupName,\n\n    [Parameter(Mandatory = $true)]\n    [string]$StorageAccountName,\n\n    [Parameter(Mandatory = $true)]\n    [string]$KeyVaultName,\n\n    [Parameter(Mandatory = $false)]\n    [string]$SecretName = \"$StorageAccountName-connection-string\",\n\n    [Parameter(Mandatory = $false)]\n    [ValidateRange(0, 3650)]\n    [int]$SecretValidityDays = 60,\n\n    [Parameter(Mandatory = $false)]\n    [switch]$UseManagedIdentity\n)\n\n$ErrorActionPreference = 'Stop'\n\nif ($UseManagedIdentity) {\n    # Do not inherit a saved context inside an Automation sandbox.\n    Disable-AzContextAutosave -Scope Process | Out-Null\n    Connect-AzAccount -Identity | Out-Null\n}\n\n$storageAccount = Get-AzStorageAccount -ResourceGroupName $ResourceGroupName -Name $StorageAccountName\n\n# AllowSharedKeyAccess is null until someone sets it; null and true both mean keys are accepted.\nif ($storageAccount.AllowSharedKeyAccess -eq $false) {\n    Write-Warning \"Shared Key authorization is disabled on $StorageAccountName. Nothing to rotate.\"\n    return\n}\n\n# Pick the older key. KeyCreationTime can be null on accounts whose keys were never rotated.\n$key1Created = $storageAccount.KeyCreationTime.Key1\n$key2Created = $storageAccount.KeyCreationTime.Key2\n\nif ($null -eq $key1Created -or $null -eq $key2Created) {\n    $keyToRotate = \"key1\"\n} elseif ($key1Created -le $key2Created) {\n    $keyToRotate = \"key1\"\n} else {\n    $keyToRotate = \"key2\"\n}\n\n$key1Text = if ($key1Created) { $key1Created.ToUniversalTime().ToString(\"yyyy-MM-ddTHH:mm:ssZ\") } else { \"unknown\" }\n$key2Text = if ($key2Created) { $key2Created.ToUniversalTime().ToString(\"yyyy-MM-ddTHH:mm:ssZ\") } else { \"unknown\" }\nWrite-Host \"key1 created $key1Text, key2 created $key2Text. Older key: $keyToRotate.\"\n\nif ($PSCmdlet.ShouldProcess($StorageAccountName, \"Regenerate $keyToRotate and write secret $SecretName\")) {\n    New-AzStorageAccountKey -ResourceGroupName $ResourceGroupName -Name $StorageAccountName -KeyName $keyToRotate | Out-Null\n\n    $newKey = Get-AzStorageAccountKey -ResourceGroupName $ResourceGroupName -Name $StorageAccountName |\n        Where-Object { $_.KeyName -eq $keyToRotate }\n\n    # Use the suffix of the signed-in environment so sovereign clouds get the right endpoint.\n    $endpointSuffix = (Get-AzContext).Environment.StorageEndpointSuffix\n    $connectionString = \"DefaultEndpointsProtocol=https;AccountName=$StorageAccountName;AccountKey=$($newKey.Value);EndpointSuffix=$endpointSuffix\"\n    $secretValue = ConvertTo-SecureString -String $connectionString -AsPlainText -Force\n\n    $secretParams = @{\n        VaultName   = $KeyVaultName\n        Name        = $SecretName\n        SecretValue = $secretValue\n        ContentType = \"text\u002Fplain; storage-connection-string\"\n        Tag         = @{ CredentialId = $keyToRotate; StorageAccount = $StorageAccountName }\n    }\n    if ($SecretValidityDays -gt 0) {\n        $secretParams[\"Expires\"] = (Get-Date).ToUniversalTime().AddDays($SecretValidityDays)\n    }\n\n    $secret = Set-AzKeyVaultSecret @secretParams\n\n    [PSCustomObject]@{\n        StorageAccount = $StorageAccountName\n        RotatedKey     = $keyToRotate\n        SecretName     = $SecretName\n        SecretVersion  = $secret.Version\n        SecretExpires  = $secret.Expires\n    }\n}\n",[15,564,565,571,578,583,590,595,600,605,610,616,622,628,639,645,655,661,671,677,687,693,703,709,719,725,733,739,747,753,759,765,771,795,804,824,838,843,860,872,877,894,906,911,929,952,957,974,994,1012,1017,1034,1045,1051,1056,1067,1072,1080,1086,1104,1120,1125,1130,1150,1155,1161,1177,1190,1196,1201,1206,1212,1223,1234,1239,1262,1273,1289,1298,1307,1317,1322,1327,1358,1383,1410,1415,1447,1471,1476,1498,1517,1522,1528,1544,1580,1606,1611,1625,1636,1647,1658,1669,1692,1698,1715,1737,1742,1747,1761,1766,1782,1793,1804,1814,1825,1836,1841],{"__ignoreMap":248},[252,566,567],{"class":254,"line":255},[252,568,570],{"class":569},"sAwPA","\u003C#\n",[252,572,573,575],{"class":254,"line":429},[252,574,180],{"class":411},[252,576,577],{"class":262},"SYNOPSIS\n",[252,579,580],{"class":254,"line":436},[252,581,582],{"class":569},"    Regenerates the older of a storage account's two access keys and writes the new connection string to Key Vault.\n",[252,584,585,587],{"class":254,"line":465},[252,586,180],{"class":411},[252,588,589],{"class":262},"DESCRIPTION\n",[252,591,592],{"class":254,"line":474},[252,593,594],{"class":569},"    Reads KeyCreationTime from the storage account to find which of key1 and key2 was created or\n",[252,596,597],{"class":254,"line":486},[252,598,599],{"class":569},"    rotated longer ago (key1 if the timestamps are missing), regenerates that key with\n",[252,601,602],{"class":254,"line":517},[252,603,604],{"class":569},"    New-AzStorageAccountKey, reads the new value back with Get-AzStorageAccountKey, builds a\n",[252,606,607],{"class":254,"line":541},[252,608,609],{"class":569},"    connection string using the endpoint suffix of the current Azure environment, and stores it as a\n",[252,611,613],{"class":254,"line":612},9,[252,614,615],{"class":569},"    new version of the named Key Vault secret with Set-AzKeyVaultSecret. The secret version gets an\n",[252,617,619],{"class":254,"line":618},10,[252,620,621],{"class":569},"    expiry date, a content type and a CredentialId tag naming the key it holds. Stops without\n",[252,623,625],{"class":254,"line":624},11,[252,626,627],{"class":569},"    rotating if the account has Shared Key authorization disabled. Supports -WhatIf and -Confirm.\n",[252,629,631,633,636],{"class":254,"line":630},12,[252,632,180],{"class":411},[252,634,635],{"class":262},"PARAMETER",[252,637,638],{"class":262}," ResourceGroupName\n",[252,640,642],{"class":254,"line":641},13,[252,643,644],{"class":569},"    Resource group that contains the storage account.\n",[252,646,648,650,652],{"class":254,"line":647},14,[252,649,180],{"class":411},[252,651,635],{"class":262},[252,653,654],{"class":262}," StorageAccountName\n",[252,656,658],{"class":254,"line":657},15,[252,659,660],{"class":569},"    Name of the storage account whose key is being rotated.\n",[252,662,664,666,668],{"class":254,"line":663},16,[252,665,180],{"class":411},[252,667,635],{"class":262},[252,669,670],{"class":262}," KeyVaultName\n",[252,672,674],{"class":254,"line":673},17,[252,675,676],{"class":569},"    Key Vault that receives the new connection string.\n",[252,678,680,682,684],{"class":254,"line":679},18,[252,681,180],{"class":411},[252,683,635],{"class":262},[252,685,686],{"class":262}," SecretName\n",[252,688,690],{"class":254,"line":689},19,[252,691,692],{"class":569},"    Secret name in the vault. Defaults to \"\u003CStorageAccountName>-connection-string\".\n",[252,694,696,698,700],{"class":254,"line":695},20,[252,697,180],{"class":411},[252,699,635],{"class":262},[252,701,702],{"class":262}," SecretValidityDays\n",[252,704,706],{"class":254,"line":705},21,[252,707,708],{"class":569},"    Sets the secret version's expiry this many days out. Default 60. Use 0 for no expiry.\n",[252,710,712,714,716],{"class":254,"line":711},22,[252,713,180],{"class":411},[252,715,635],{"class":262},[252,717,718],{"class":262}," UseManagedIdentity\n",[252,720,722],{"class":254,"line":721},23,[252,723,724],{"class":569},"    Signs in with Connect-AzAccount -Identity first (for Automation runbooks).\n",[252,726,728,730],{"class":254,"line":727},24,[252,729,180],{"class":411},[252,731,732],{"class":262},"EXAMPLE\n",[252,734,736],{"class":254,"line":735},25,[252,737,738],{"class":569},"    .\\Rotate-StorageAccountKey.ps1 -ResourceGroupName \"rg-prod-data\" -StorageAccountName \"stprodreports01\" -KeyVaultName \"kv-prod-secrets\" -WhatIf\n",[252,740,742,744],{"class":254,"line":741},26,[252,743,180],{"class":411},[252,745,746],{"class":262},"NOTES\n",[252,748,750],{"class":254,"line":749},27,[252,751,752],{"class":569},"    Author  : Thomas Lasswell (https:\u002F\u002Fwww.techcolumnist.com)\n",[252,754,756],{"class":254,"line":755},28,[252,757,758],{"class":569},"    Version : 1.0 (2025-04-24)\n",[252,760,762],{"class":254,"line":761},29,[252,763,764],{"class":569},"    Requires: Az.Accounts, Az.Storage, Az.KeyVault\n",[252,766,768],{"class":254,"line":767},30,[252,769,770],{"class":569},"#>\n",[252,772,774,777,780,783,786,789,792],{"class":254,"line":773},31,[252,775,776],{"class":258},"[",[252,778,779],{"class":411},"CmdletBinding",[252,781,782],{"class":258},"(",[252,784,234],{"class":785},"s9osk",[252,787,788],{"class":262}," =",[252,790,791],{"class":411}," $true",[252,793,794],{"class":258},")]\n",[252,796,798,801],{"class":254,"line":797},32,[252,799,800],{"class":262},"param",[252,802,803],{"class":258},"(\n",[252,805,807,810,813,815,818,820,822],{"class":254,"line":806},33,[252,808,809],{"class":258},"    [",[252,811,812],{"class":411},"Parameter",[252,814,782],{"class":258},[252,816,817],{"class":785},"Mandatory",[252,819,788],{"class":262},[252,821,791],{"class":411},[252,823,794],{"class":258},[252,825,827,829,832,835],{"class":254,"line":826},34,[252,828,809],{"class":258},[252,830,831],{"class":262},"string",[252,833,834],{"class":258},"]$ResourceGroupName",[252,836,837],{"class":262},",\n",[252,839,841],{"class":254,"line":840},35,[252,842,433],{"emptyLinePlaceholder":432},[252,844,846,848,850,852,854,856,858],{"class":254,"line":845},36,[252,847,809],{"class":258},[252,849,812],{"class":411},[252,851,782],{"class":258},[252,853,817],{"class":785},[252,855,788],{"class":262},[252,857,791],{"class":411},[252,859,794],{"class":258},[252,861,863,865,867,870],{"class":254,"line":862},37,[252,864,809],{"class":258},[252,866,831],{"class":262},[252,868,869],{"class":258},"]$StorageAccountName",[252,871,837],{"class":262},[252,873,875],{"class":254,"line":874},38,[252,876,433],{"emptyLinePlaceholder":432},[252,878,880,882,884,886,888,890,892],{"class":254,"line":879},39,[252,881,809],{"class":258},[252,883,812],{"class":411},[252,885,782],{"class":258},[252,887,817],{"class":785},[252,889,788],{"class":262},[252,891,791],{"class":411},[252,893,794],{"class":258},[252,895,897,899,901,904],{"class":254,"line":896},40,[252,898,809],{"class":258},[252,900,831],{"class":262},[252,902,903],{"class":258},"]$KeyVaultName",[252,905,837],{"class":262},[252,907,909],{"class":254,"line":908},41,[252,910,433],{"emptyLinePlaceholder":432},[252,912,914,916,918,920,922,924,927],{"class":254,"line":913},42,[252,915,809],{"class":258},[252,917,812],{"class":411},[252,919,782],{"class":258},[252,921,817],{"class":785},[252,923,788],{"class":262},[252,925,926],{"class":411}," $false",[252,928,794],{"class":258},[252,930,932,934,936,939,941,944,947,950],{"class":254,"line":931},43,[252,933,809],{"class":258},[252,935,831],{"class":262},[252,937,938],{"class":258},"]$SecretName ",[252,940,408],{"class":262},[252,942,943],{"class":274}," \"",[252,945,946],{"class":258},"$StorageAccountName",[252,948,949],{"class":274},"-connection-string\"",[252,951,837],{"class":262},[252,953,955],{"class":254,"line":954},44,[252,956,433],{"emptyLinePlaceholder":432},[252,958,960,962,964,966,968,970,972],{"class":254,"line":959},45,[252,961,809],{"class":258},[252,963,812],{"class":411},[252,965,782],{"class":258},[252,967,817],{"class":785},[252,969,788],{"class":262},[252,971,926],{"class":411},[252,973,794],{"class":258},[252,975,977,979,982,984,986,989,992],{"class":254,"line":976},46,[252,978,809],{"class":258},[252,980,981],{"class":411},"ValidateRange",[252,983,782],{"class":258},[252,985,202],{"class":411},[252,987,988],{"class":262},",",[252,990,991],{"class":411}," 3650",[252,993,794],{"class":258},[252,995,997,999,1002,1005,1007,1010],{"class":254,"line":996},47,[252,998,809],{"class":258},[252,1000,1001],{"class":262},"int",[252,1003,1004],{"class":258},"]$SecretValidityDays ",[252,1006,408],{"class":262},[252,1008,1009],{"class":411}," 60",[252,1011,837],{"class":262},[252,1013,1015],{"class":254,"line":1014},48,[252,1016,433],{"emptyLinePlaceholder":432},[252,1018,1020,1022,1024,1026,1028,1030,1032],{"class":254,"line":1019},49,[252,1021,809],{"class":258},[252,1023,812],{"class":411},[252,1025,782],{"class":258},[252,1027,817],{"class":785},[252,1029,788],{"class":262},[252,1031,926],{"class":411},[252,1033,794],{"class":258},[252,1035,1037,1039,1042],{"class":254,"line":1036},50,[252,1038,809],{"class":258},[252,1040,1041],{"class":262},"switch",[252,1043,1044],{"class":258},"]$UseManagedIdentity\n",[252,1046,1048],{"class":254,"line":1047},51,[252,1049,1050],{"class":258},")\n",[252,1052,1054],{"class":254,"line":1053},52,[252,1055,433],{"emptyLinePlaceholder":432},[252,1057,1059,1062,1064],{"class":254,"line":1058},53,[252,1060,1061],{"class":411},"$ErrorActionPreference",[252,1063,788],{"class":262},[252,1065,1066],{"class":274}," 'Stop'\n",[252,1068,1070],{"class":254,"line":1069},54,[252,1071,433],{"emptyLinePlaceholder":432},[252,1073,1075,1077],{"class":254,"line":1074},55,[252,1076,439],{"class":262},[252,1078,1079],{"class":258}," ($UseManagedIdentity) {\n",[252,1081,1083],{"class":254,"line":1082},56,[252,1084,1085],{"class":569},"    # Do not inherit a saved context inside an Automation sandbox.\n",[252,1087,1089,1092,1094,1097,1100,1102],{"class":254,"line":1088},57,[252,1090,1091],{"class":411},"    Disable-AzContextAutosave",[252,1093,278],{"class":262},[252,1095,1096],{"class":258},"Scope ",[252,1098,1099],{"class":262},"Process",[252,1101,511],{"class":262},[252,1103,514],{"class":411},[252,1105,1107,1110,1112,1115,1118],{"class":254,"line":1106},58,[252,1108,1109],{"class":411},"    Connect-AzAccount",[252,1111,278],{"class":262},[252,1113,1114],{"class":258},"Identity ",[252,1116,1117],{"class":262},"|",[252,1119,514],{"class":411},[252,1121,1123],{"class":254,"line":1122},59,[252,1124,544],{"class":258},[252,1126,1128],{"class":254,"line":1127},60,[252,1129,433],{"emptyLinePlaceholder":432},[252,1131,1133,1136,1138,1140,1142,1145,1147],{"class":254,"line":1132},61,[252,1134,1135],{"class":258},"$storageAccount ",[252,1137,408],{"class":262},[252,1139,412],{"class":411},[252,1141,278],{"class":262},[252,1143,1144],{"class":258},"ResourceGroupName $ResourceGroupName ",[252,1146,263],{"class":262},[252,1148,1149],{"class":258},"Name $StorageAccountName\n",[252,1151,1153],{"class":254,"line":1152},62,[252,1154,433],{"emptyLinePlaceholder":432},[252,1156,1158],{"class":254,"line":1157},63,[252,1159,1160],{"class":569},"# AllowSharedKeyAccess is null until someone sets it; null and true both mean keys are accepted.\n",[252,1162,1164,1166,1169,1172,1174],{"class":254,"line":1163},64,[252,1165,439],{"class":262},[252,1167,1168],{"class":258}," ($storageAccount.AllowSharedKeyAccess ",[252,1170,1171],{"class":262},"-eq",[252,1173,926],{"class":411},[252,1175,1176],{"class":258},") {\n",[252,1178,1180,1182,1185,1187],{"class":254,"line":1179},65,[252,1181,468],{"class":411},[252,1183,1184],{"class":274}," \"Shared Key authorization is disabled on ",[252,1186,946],{"class":258},[252,1188,1189],{"class":274},". Nothing to rotate.\"\n",[252,1191,1193],{"class":254,"line":1192},66,[252,1194,1195],{"class":262},"    return\n",[252,1197,1199],{"class":254,"line":1198},67,[252,1200,544],{"class":258},[252,1202,1204],{"class":254,"line":1203},68,[252,1205,433],{"emptyLinePlaceholder":432},[252,1207,1209],{"class":254,"line":1208},69,[252,1210,1211],{"class":569},"# Pick the older key. KeyCreationTime can be null on accounts whose keys were never rotated.\n",[252,1213,1215,1218,1220],{"class":254,"line":1214},70,[252,1216,1217],{"class":258},"$key1Created ",[252,1219,408],{"class":262},[252,1221,1222],{"class":258}," $storageAccount.KeyCreationTime.Key1\n",[252,1224,1226,1229,1231],{"class":254,"line":1225},71,[252,1227,1228],{"class":258},"$key2Created ",[252,1230,408],{"class":262},[252,1232,1233],{"class":258}," $storageAccount.KeyCreationTime.Key2\n",[252,1235,1237],{"class":254,"line":1236},72,[252,1238,433],{"emptyLinePlaceholder":432},[252,1240,1242,1244,1246,1248,1250,1253,1255,1257,1259],{"class":254,"line":1241},73,[252,1243,439],{"class":262},[252,1245,442],{"class":258},[252,1247,445],{"class":411},[252,1249,448],{"class":262},[252,1251,1252],{"class":258}," $key1Created ",[252,1254,454],{"class":262},[252,1256,457],{"class":411},[252,1258,448],{"class":262},[252,1260,1261],{"class":258}," $key2Created) {\n",[252,1263,1265,1268,1270],{"class":254,"line":1264},74,[252,1266,1267],{"class":258},"    $keyToRotate ",[252,1269,408],{"class":262},[252,1271,1272],{"class":274}," \"key1\"\n",[252,1274,1276,1278,1281,1284,1287],{"class":254,"line":1275},75,[252,1277,477],{"class":258},[252,1279,1280],{"class":262},"elseif",[252,1282,1283],{"class":258}," ($key1Created ",[252,1285,1286],{"class":262},"-le",[252,1288,1261],{"class":258},[252,1290,1292,1294,1296],{"class":254,"line":1291},76,[252,1293,1267],{"class":258},[252,1295,408],{"class":262},[252,1297,1272],{"class":274},[252,1299,1301,1303,1305],{"class":254,"line":1300},77,[252,1302,477],{"class":258},[252,1304,480],{"class":262},[252,1306,483],{"class":258},[252,1308,1310,1312,1314],{"class":254,"line":1309},78,[252,1311,1267],{"class":258},[252,1313,408],{"class":262},[252,1315,1316],{"class":274}," \"key2\"\n",[252,1318,1320],{"class":254,"line":1319},79,[252,1321,544],{"class":258},[252,1323,1325],{"class":254,"line":1324},80,[252,1326,433],{"emptyLinePlaceholder":432},[252,1328,1330,1333,1335,1338,1341,1344,1347,1349,1352,1355],{"class":254,"line":1329},81,[252,1331,1332],{"class":258},"$key1Text ",[252,1334,408],{"class":262},[252,1336,1337],{"class":262}," if",[252,1339,1340],{"class":258}," ($key1Created) { $key1Created.ToUniversalTime().ToString(",[252,1342,1343],{"class":274},"\"yyyy-MM-ddTHH:mm:ssZ\"",[252,1345,1346],{"class":258},") } ",[252,1348,480],{"class":262},[252,1350,1351],{"class":258}," { ",[252,1353,1354],{"class":274},"\"unknown\"",[252,1356,1357],{"class":258}," }\n",[252,1359,1361,1364,1366,1368,1371,1373,1375,1377,1379,1381],{"class":254,"line":1360},82,[252,1362,1363],{"class":258},"$key2Text ",[252,1365,408],{"class":262},[252,1367,1337],{"class":262},[252,1369,1370],{"class":258}," ($key2Created) { $key2Created.ToUniversalTime().ToString(",[252,1372,1343],{"class":274},[252,1374,1346],{"class":258},[252,1376,480],{"class":262},[252,1378,1351],{"class":258},[252,1380,1354],{"class":274},[252,1382,1357],{"class":258},[252,1384,1386,1389,1392,1395,1398,1401,1404,1407],{"class":254,"line":1385},83,[252,1387,1388],{"class":411},"Write-Host",[252,1390,1391],{"class":274}," \"key1 created ",[252,1393,1394],{"class":258},"$key1Text",[252,1396,1397],{"class":274},", key2 created ",[252,1399,1400],{"class":258},"$key2Text",[252,1402,1403],{"class":274},". Older key: ",[252,1405,1406],{"class":258},"$keyToRotate",[252,1408,1409],{"class":274},".\"\n",[252,1411,1413],{"class":254,"line":1412},84,[252,1414,433],{"emptyLinePlaceholder":432},[252,1416,1418,1420,1422,1425,1428,1430,1433,1435,1438,1441,1444],{"class":254,"line":1417},85,[252,1419,439],{"class":262},[252,1421,442],{"class":258},[252,1423,1424],{"class":411},"$PSCmdlet",[252,1426,1427],{"class":258},".ShouldProcess($StorageAccountName",[252,1429,988],{"class":262},[252,1431,1432],{"class":274}," \"Regenerate ",[252,1434,1406],{"class":258},[252,1436,1437],{"class":274}," and write secret ",[252,1439,1440],{"class":258},"$SecretName",[252,1442,1443],{"class":274},"\"",[252,1445,1446],{"class":258},")) {\n",[252,1448,1450,1453,1455,1457,1459,1462,1464,1467,1469],{"class":254,"line":1449},86,[252,1451,1452],{"class":411},"    New-AzStorageAccountKey",[252,1454,278],{"class":262},[252,1456,1144],{"class":258},[252,1458,263],{"class":262},[252,1460,1461],{"class":258},"Name $StorageAccountName ",[252,1463,263],{"class":262},[252,1465,1466],{"class":258},"KeyName $keyToRotate ",[252,1468,1117],{"class":262},[252,1470,514],{"class":411},[252,1472,1474],{"class":254,"line":1473},87,[252,1475,433],{"emptyLinePlaceholder":432},[252,1477,1479,1482,1484,1487,1489,1491,1493,1495],{"class":254,"line":1478},88,[252,1480,1481],{"class":258},"    $newKey ",[252,1483,408],{"class":262},[252,1485,1486],{"class":411}," Get-AzStorageAccountKey",[252,1488,278],{"class":262},[252,1490,1144],{"class":258},[252,1492,263],{"class":262},[252,1494,1461],{"class":258},[252,1496,1497],{"class":262},"|\n",[252,1499,1501,1504,1506,1509,1512,1514],{"class":254,"line":1500},89,[252,1502,1503],{"class":411},"        Where-Object",[252,1505,1351],{"class":258},[252,1507,1508],{"class":411},"$_",[252,1510,1511],{"class":258},".KeyName ",[252,1513,1171],{"class":262},[252,1515,1516],{"class":258}," $keyToRotate }\n",[252,1518,1520],{"class":254,"line":1519},90,[252,1521,433],{"emptyLinePlaceholder":432},[252,1523,1525],{"class":254,"line":1524},91,[252,1526,1527],{"class":569},"    # Use the suffix of the signed-in environment so sovereign clouds get the right endpoint.\n",[252,1529,1531,1534,1536,1538,1541],{"class":254,"line":1530},92,[252,1532,1533],{"class":258},"    $endpointSuffix ",[252,1535,408],{"class":262},[252,1537,442],{"class":258},[252,1539,1540],{"class":411},"Get-AzContext",[252,1542,1543],{"class":258},").Environment.StorageEndpointSuffix\n",[252,1545,1547,1550,1552,1555,1557,1560,1563,1565,1568,1571,1574,1577],{"class":254,"line":1546},93,[252,1548,1549],{"class":258},"    $connectionString ",[252,1551,408],{"class":262},[252,1553,1554],{"class":274}," \"DefaultEndpointsProtocol=https;AccountName=",[252,1556,946],{"class":258},[252,1558,1559],{"class":274},";AccountKey=",[252,1561,1562],{"class":262},"$",[252,1564,782],{"class":274},[252,1566,1567],{"class":258},"$newKey.Value",[252,1569,1570],{"class":274},")",[252,1572,1573],{"class":274},";EndpointSuffix=",[252,1575,1576],{"class":258},"$endpointSuffix",[252,1578,1579],{"class":274},"\"\n",[252,1581,1583,1586,1588,1591,1593,1596,1598,1601,1603],{"class":254,"line":1582},94,[252,1584,1585],{"class":258},"    $secretValue ",[252,1587,408],{"class":262},[252,1589,1590],{"class":411}," ConvertTo-SecureString",[252,1592,278],{"class":262},[252,1594,1595],{"class":258},"String $connectionString ",[252,1597,263],{"class":262},[252,1599,1600],{"class":258},"AsPlainText ",[252,1602,263],{"class":262},[252,1604,1605],{"class":258},"Force\n",[252,1607,1609],{"class":254,"line":1608},95,[252,1610,433],{"emptyLinePlaceholder":432},[252,1612,1614,1617,1619,1622],{"class":254,"line":1613},96,[252,1615,1616],{"class":258},"    $secretParams ",[252,1618,408],{"class":262},[252,1620,1621],{"class":262}," @",[252,1623,1624],{"class":258},"{\n",[252,1626,1628,1631,1633],{"class":254,"line":1627},97,[252,1629,1630],{"class":258},"        VaultName   ",[252,1632,408],{"class":262},[252,1634,1635],{"class":258}," $KeyVaultName\n",[252,1637,1639,1642,1644],{"class":254,"line":1638},98,[252,1640,1641],{"class":258},"        Name        ",[252,1643,408],{"class":262},[252,1645,1646],{"class":258}," $SecretName\n",[252,1648,1650,1653,1655],{"class":254,"line":1649},99,[252,1651,1652],{"class":258},"        SecretValue ",[252,1654,408],{"class":262},[252,1656,1657],{"class":258}," $secretValue\n",[252,1659,1661,1664,1666],{"class":254,"line":1660},100,[252,1662,1663],{"class":258},"        ContentType ",[252,1665,408],{"class":262},[252,1667,1668],{"class":274}," \"text\u002Fplain; storage-connection-string\"\n",[252,1670,1672,1675,1677,1679,1682,1684,1687,1689],{"class":254,"line":1671},101,[252,1673,1674],{"class":258},"        Tag         ",[252,1676,408],{"class":262},[252,1678,1621],{"class":262},[252,1680,1681],{"class":258},"{ CredentialId ",[252,1683,408],{"class":262},[252,1685,1686],{"class":258}," $keyToRotate; StorageAccount ",[252,1688,408],{"class":262},[252,1690,1691],{"class":258}," $StorageAccountName }\n",[252,1693,1695],{"class":254,"line":1694},102,[252,1696,1697],{"class":258},"    }\n",[252,1699,1701,1704,1707,1710,1713],{"class":254,"line":1700},103,[252,1702,1703],{"class":262},"    if",[252,1705,1706],{"class":258}," ($SecretValidityDays ",[252,1708,1709],{"class":262},"-gt",[252,1711,1712],{"class":411}," 0",[252,1714,1176],{"class":258},[252,1716,1718,1721,1724,1727,1729,1731,1734],{"class":254,"line":1717},104,[252,1719,1720],{"class":258},"        $secretParams[",[252,1722,1723],{"class":274},"\"Expires\"",[252,1725,1726],{"class":258},"] ",[252,1728,408],{"class":262},[252,1730,442],{"class":258},[252,1732,1733],{"class":411},"Get-Date",[252,1735,1736],{"class":258},").ToUniversalTime().AddDays($SecretValidityDays)\n",[252,1738,1740],{"class":254,"line":1739},105,[252,1741,1697],{"class":258},[252,1743,1745],{"class":254,"line":1744},106,[252,1746,433],{"emptyLinePlaceholder":432},[252,1748,1750,1753,1755,1758],{"class":254,"line":1749},107,[252,1751,1752],{"class":258},"    $secret ",[252,1754,408],{"class":262},[252,1756,1757],{"class":411}," Set-AzKeyVaultSecret",[252,1759,1760],{"class":258}," @secretParams\n",[252,1762,1764],{"class":254,"line":1763},108,[252,1765,433],{"emptyLinePlaceholder":432},[252,1767,1769,1771,1774,1777,1780],{"class":254,"line":1768},109,[252,1770,809],{"class":258},[252,1772,1773],{"class":262},"PSCustomObject",[252,1775,1776],{"class":258},"]",[252,1778,1779],{"class":262},"@",[252,1781,1624],{"class":258},[252,1783,1785,1788,1790],{"class":254,"line":1784},110,[252,1786,1787],{"class":258},"        StorageAccount ",[252,1789,408],{"class":262},[252,1791,1792],{"class":258}," $StorageAccountName\n",[252,1794,1796,1799,1801],{"class":254,"line":1795},111,[252,1797,1798],{"class":258},"        RotatedKey     ",[252,1800,408],{"class":262},[252,1802,1803],{"class":258}," $keyToRotate\n",[252,1805,1807,1810,1812],{"class":254,"line":1806},112,[252,1808,1809],{"class":258},"        SecretName     ",[252,1811,408],{"class":262},[252,1813,1646],{"class":258},[252,1815,1817,1820,1822],{"class":254,"line":1816},113,[252,1818,1819],{"class":258},"        SecretVersion  ",[252,1821,408],{"class":262},[252,1823,1824],{"class":258}," $secret.Version\n",[252,1826,1828,1831,1833],{"class":254,"line":1827},114,[252,1829,1830],{"class":258},"        SecretExpires  ",[252,1832,408],{"class":262},[252,1834,1835],{"class":258}," $secret.Expires\n",[252,1837,1839],{"class":254,"line":1838},115,[252,1840,1697],{"class":258},[252,1842,1844],{"class":254,"line":1843},116,[252,1845,544],{"class":258},[31,1847,1849],{"id":1848},"notes","Notes",[36,1851,1852,1855,1862,1868,1871,1876,1895,1908],{},[39,1853,1854],{},"The script never regenerates both keys in one run, so a client holding the untouched key keeps working. That only helps if every client uses the same key: Microsoft's rotation guidance warns that if some applications use key1 and others key2, you cannot rotate without one of them losing access. Point every consumer at the Key Vault secret and nothing else.",[39,1856,1857,1858,1861],{},"Regenerating a key also revokes every ",[58,1859,1860],{},"account SAS and service SAS"," signed with it. User delegation SAS tokens are signed with Entra credentials and survive rotation, which is one more reason to move SAS issuance to user delegation.",[39,1863,1864,1865,1867],{},"On the very first run, clients that already hold key1 lose access, because key1 is picked when ",[15,1866,17],{}," is empty. Follow Microsoft's manual order for the first cycle: move clients to key2 (or to the Key Vault secret seeded with key2), then run the script.",[39,1869,1870],{},"Consumers that read the secret once at startup keep the previous value until they restart. The previous secret version holds the other key, which this run did not touch; it becomes the older key and is regenerated on the next run. With a monthly schedule, every consumer therefore has a month to pick up the new version. Services that resolve the secret through Key Vault references or re-read it on a timer need no restart.",[39,1872,1873,1875],{},[15,1874,17],{}," replaces the tag-based state an earlier draft of this script kept on the storage account. The timestamp is maintained by Azure, cannot drift, and is the same value the key expiration policy uses.",[39,1877,1878,1879,1882,1883,1886,1887,1890,1891,1894],{},"The connection string is built with ",[15,1880,1881],{},"EndpointSuffix"," from ",[15,1884,1885],{},"(Get-AzContext).Environment.StorageEndpointSuffix"," (for example ",[15,1888,1889],{},"core.windows.net"," in public Azure, ",[15,1892,1893],{},"core.chinacloudapi.cn"," in Azure China), instead of hard-coding the public cloud suffix.",[39,1896,1897,442,1900,1903,1904,1907],{},[58,1898,1899],{},"Key Vault managed storage account keys",[15,1901,1902],{},"Add-AzKeyVaultManagedStorageAccount",") look like the built-in answer to this problem, but Microsoft lists the feature as legacy, supported as-is with no further updates, and points to Entra ID authorization instead. For a fully event-driven alternative to a schedule, Microsoft's dual-credential rotation tutorial uses the Key Vault ",[15,1905,1906],{},"SecretNearExpiry"," Event Grid event (published 30 days before a secret expires) to trigger a Function that regenerates the alternate key.",[39,1909,1910,1911,1914,1915,1918,1919,1922,1923,1926,1927,1930],{},"To see who still uses the keys before disabling them, filter the storage account's ",[58,1912,1913],{},"Transactions"," metric by the ",[58,1916,1917],{},"Authentication"," dimension (Account Key and SAS), or query ",[15,1920,1921],{},"StorageBlobLogs"," for ",[15,1924,1925],{},"AuthenticationType in (\"AccountKey\", \"SAS\")"," once a diagnostic setting sends resource logs to Log Analytics. Assign ",[58,1928,1929],{},"Storage accounts should prevent shared key access"," in Audit mode first, then switch it to Deny once clients are migrated.",[31,1932,1934],{"id":1933},"source","Source",[36,1936,1937,1946,1953,1960,1967,1974,1991],{},[39,1938,1939],{},[1940,1941,1945],"a",{"href":1942,"rel":1943},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fazure\u002Fstorage\u002Fcommon\u002Fstorage-account-keys-manage",[1944],"nofollow","Manage storage account access keys (rotation, key expiration policy)",[39,1947,1948],{},[1940,1949,1952],{"href":1950,"rel":1951},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fazure\u002Fstorage\u002Fcommon\u002Fshared-key-authorization-prevent",[1944],"Prevent Shared Key authorization for an Azure Storage account",[39,1954,1955],{},[1940,1956,1959],{"href":1957,"rel":1958},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fazure\u002Fstorage\u002Fcommon\u002Fauthorize-data-access",[1944],"Authorize operations for data access in Azure Storage",[39,1961,1962],{},[1940,1963,1966],{"href":1964,"rel":1965},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fazure\u002Fkey-vault\u002Fsecrets\u002Ftutorial-rotation-dual",[1944],"Rotation tutorial for resources with two sets of credentials (Key Vault)",[39,1968,1969],{},[1940,1970,1973],{"href":1971,"rel":1972},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fazure\u002Fkey-vault\u002Fsecrets\u002Foverview-storage-keys-powershell",[1944],"Azure Key Vault managed storage account (legacy)",[39,1975,1976,45,1981,45,1986],{},[1940,1977,1980],{"href":1978,"rel":1979},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fpowershell\u002Fmodule\u002Faz.storage\u002Fnew-azstorageaccountkey",[1944],"New-AzStorageAccountKey",[1940,1982,1985],{"href":1983,"rel":1984},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fpowershell\u002Fmodule\u002Faz.storage\u002Fset-azstorageaccount",[1944],"Set-AzStorageAccount",[1940,1987,1990],{"href":1988,"rel":1989},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fpowershell\u002Fmodule\u002Faz.keyvault\u002Fset-azkeyvaultsecret",[1944],"Set-AzKeyVaultSecret",[39,1992,1993],{},[1940,1994,1997],{"href":1995,"rel":1996},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fazure\u002Fautomation\u002Fenable-managed-identity-for-automation",[1944],"Using a system-assigned managed identity for an Azure Automation account",[1999,2000,2001],"style",{},"html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":248,"searchDepth":429,"depth":429,"links":2003},[2004,2005,2006,2007,2008,2009],{"id":33,"depth":429,"text":34},{"id":90,"depth":429,"text":91},{"id":237,"depth":429,"text":238},{"id":558,"depth":429,"text":559},{"id":1848,"depth":429,"text":1849},{"id":1933,"depth":429,"text":1934},"techcolumnist",[2012,2013],"ps","scripts","2025-04-24T14:00:00Z","A runbook-ready script that regenerates the older of a storage account's two access keys, using KeyCreationTime, and writes the new connection string to Key Vault.","md",false,null,{},"\u002Fblog\u002F2025\u002F04\u002F24\u002Fpowershell-azure-rotate-storage-account-access-keys-on-a-schedule",{"title":6,"description":2015},[2010],"blog\u002F2025\u002F04\u002F24\u002Fpowershell-azure-rotate-storage-account-access-keys-on-a-schedule",[247,2025],"azure","\u002F2025\u002F04\u002F24\u002Fpowershell-azure-rotate-storage-account-access-keys-on-a-schedule\u002F","AWJOnmQlL7tn9k-aaff-Vsgy6xE5Cq7U4KxnM0GPT_g",{"title":2029,"description":2030,"date":2031,"url":2032,"categories":2033,"tags":2035,"image":2036,"readingTime":630,"canonical":2037,"sites":2038,"series":2039,"seriesOrder":255},"Why ITSM Still Matters in a Cloud-Native, Agile, Multi-Framework World","Technology is no longer a back-office function; it’s the front door to your customer, your employee experience, and your ability to scale.","2025-05-01T01:43:15Z","\u002F2025\u002F05\u002F01\u002Fwhy-itsm-still-matters-in-a-cloud-native-agile-multi-framework-world\u002F",[2034],"business",[],"\u002Fuploads\u002F2025\u002F05\u002FChatGPT-Image-Apr-30-2025-09_33_34-PM.png","lasswell",[2037,2010],"itsm-in-practice",{"title":2041,"description":2042,"date":2043,"url":2044,"categories":2045,"tags":2046,"image":2018,"readingTime":436,"canonical":2010,"sites":2049,"series":2018,"seriesOrder":2018},"PowerShell: Intune – Bulk Assign App Protection Policies by Group","A Microsoft Graph PowerShell script that adds Entra ID groups to an Intune App Protection Policy through the v1.0 assign action without dropping existing assignments.","2025-04-17T14:00:00Z","\u002F2025\u002F04\u002F17\u002Fpowershell-intune-bulk-assign-app-protection-policies-by-group\u002F",[2012,2013],[247,2047,2048],"intune","entra-id",[2010],[2051,2059,2068],{"title":2052,"description":2053,"date":2054,"url":2055,"categories":2056,"tags":2057,"image":2018,"readingTime":465,"canonical":2010,"sites":2058,"series":2018,"seriesOrder":2018},"PowerShell: Azure – Audit Who Has Owner Role Across All Subscriptions","A script that walks every subscription a credential can see and reports every Owner assignment, active or PIM-eligible, by the scope it was granted at.","2025-11-19T14:00:00Z","\u002F2025\u002F11\u002F19\u002Fpowershell-azure-audit-who-has-owner-role-across-all-subscriptions\u002F",[2012,2013],[247,2025],[2010],{"title":2060,"description":2061,"date":2062,"url":2063,"categories":2064,"tags":2065,"image":2018,"readingTime":465,"canonical":2010,"sites":2067,"series":2018,"seriesOrder":2018},"PowerShell: Azure – Tag Every Untagged Resource in a Subscription","A PowerShell sweep that finds untagged Azure resources and adds only the missing resource group tags, plus the built-in Azure Policy that does it going forward.","2025-09-24T14:00:00Z","\u002F2025\u002F09\u002F24\u002Fpowershell-azure-tag-every-untagged-resource-in-a-subscription\u002F",[2012,2013],[247,2025,2066],"discovery",[2010],{"title":2069,"description":2070,"date":2071,"url":2072,"categories":2073,"tags":2074,"image":2018,"readingTime":474,"canonical":2010,"sites":2077,"series":2018,"seriesOrder":2018},"PowerShell: Active Directory – Report Group Membership Sprawl","Rank AD security groups by nesting depth, transitive member count, ownership and staleness for an access review, plus a per-user Kerberos token size estimate.","2025-12-03T14:00:00Z","\u002F2025\u002F12\u002F03\u002Fpowershell-active-directory-report-group-membership-sprawl\u002F",[2012,2013],[247,2075,2076],"active-directory","reporting",[2010],{"doc":2018,"posts":2079},[],1790052512579]