174 строки
6.1 KiB
PowerShell
174 строки
6.1 KiB
PowerShell
|
### Example
|
||
|
## Add Administrator: .\config-edit.ps1 -query '.security.users.administrators |= . + [\"USER\"]'
|
||
|
## Add Cors: .\config-edit.ps1 -query '.cors.rules |= . + [{\"origin\": \"URL\", "allow": true }]'
|
||
|
|
||
|
## Note that because of how powershell and jq escape sequence works, if you were to use double quoted string instead of single quote
|
||
|
## you would need to escape double quotation twice, etc: """" => ", """"my name""""" => "my name"
|
||
|
|
||
|
#Requires -RunAsAdministrator
|
||
|
[CmdletBinding()]
|
||
|
param(
|
||
|
[string]
|
||
|
$serviceName = "Microsoft IIS Administration",
|
||
|
|
||
|
[string]
|
||
|
$jqVersion = "1.6",
|
||
|
|
||
|
[string]
|
||
|
$jqSource = "https://github.com/stedolan/jq/releases/download/jq-{0}/jq-win{1}.exe",
|
||
|
|
||
|
[string]
|
||
|
$jqTarget,
|
||
|
|
||
|
[Parameter(Mandatory=$true)]
|
||
|
[string]
|
||
|
$query,
|
||
|
|
||
|
[switch]
|
||
|
$quiet,
|
||
|
|
||
|
[string]
|
||
|
$administratorsSID = 'S-1-5-32-544',
|
||
|
|
||
|
[switch]
|
||
|
$wait
|
||
|
)
|
||
|
|
||
|
$ErrorActionPreference = "Stop"
|
||
|
|
||
|
function EnsureJQ {
|
||
|
if ((!$jqTarget) -and (Get-Command "jq" -ErrorAction SilentlyContinue)) {
|
||
|
return "jq"
|
||
|
} else {
|
||
|
if ([Environment]::Is64BitProcess) {
|
||
|
$bitness = 64
|
||
|
} else {
|
||
|
$bitness = 32
|
||
|
}
|
||
|
$jqPath = $jqTarget;
|
||
|
if (!$jqPath) {
|
||
|
$jqPath = Join-Path $env:TEMP "jq.exe"
|
||
|
}
|
||
|
$downloadFrom = $jqSource -f $jqVersion, $bitness
|
||
|
if (!(Test-Path $jqPath)) {
|
||
|
Invoke-WebRequest -Uri $downloadFrom -OutFile $jqPath
|
||
|
}
|
||
|
return $jqPath
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function LogVerbose($msg){
|
||
|
Write-Verbose $msg
|
||
|
}
|
||
|
|
||
|
function GetIISAdminHome($procs) {
|
||
|
foreach ($proc in $procs) {
|
||
|
$iisMainModule = $proc.Modules | Where-Object { $_.ModuleName -eq "Microsoft.IIS.Administration.dll" }
|
||
|
if ($iisMainModule) {
|
||
|
LogVerbose "IIS Admin module found at $($iisMainModule.FileName)"
|
||
|
return Split-Path $iisMainModule.FileName
|
||
|
}
|
||
|
}
|
||
|
throw "Unable to locate IIS Admin Home"
|
||
|
}
|
||
|
|
||
|
function ConvertTo-NTAccount($From)
|
||
|
{
|
||
|
if ($From -is [System.Security.Principal.NTAccount]) {
|
||
|
return $From
|
||
|
}
|
||
|
if ($From -is [System.Security.Principal.SecurityIdentifier]) {
|
||
|
return ($From.Translate([System.Security.Principal.NTAccount]))
|
||
|
}
|
||
|
if (!($From -is [string])) {
|
||
|
Throw "Don't know how to convert an object of type '$($From.GetType())' to an NTAccount"
|
||
|
}
|
||
|
try {
|
||
|
# Try the symbolic format first.
|
||
|
# For the symbolic format, translate twice, to make sure that
|
||
|
# the value is valid.
|
||
|
$acc = new-object System.Security.Principal.NTAccount($From)
|
||
|
$sid = $acc.Translate([System.Security.Principal.SecurityIdentifier])
|
||
|
return ($sid.Translate([System.Security.Principal.NTAccount]))
|
||
|
} catch {
|
||
|
$sid = new-object System.Security.Principal.SecurityIdentifier($From)
|
||
|
return ($sid.Translate([System.Security.Principal.NTAccount]))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
function EnsureAcl($workingDirectory) {
|
||
|
$apiHome = [System.IO.Path]::Combine($workingDirectory, "..")
|
||
|
$modifyAcess = [System.Security.AccessControl.FileSystemRights]::Modify
|
||
|
$allow = [System.Security.AccessControl.AccessControlType]::Allow
|
||
|
$builtInAdministrators = (ConvertTo-NTAccount $administratorsSID).value
|
||
|
$dirAcl = Get-Acl $apiHome
|
||
|
$dirAccessGranted = $dirAcl.Access | Where-Object { ($_.IdentityReference.Value -eq $builtInAdministrators) -and ($_.AccessControlType -eq $allow) -and (($_.FileSystemRights -bAnd $modifyAcess) -eq $modifyAcess) }
|
||
|
if (!$dirAccessGranted) {
|
||
|
if (!$quiet) {
|
||
|
$confirm = Read-Host "$builtInAdministrators will PERMANENTLY gain modify access to $apiHome, proceed? (Y/n)"
|
||
|
if ($confirm -ne "y") {
|
||
|
throw "User cancelled"
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$dirAccess = ($dirAcl.Access | Where-Object { ($_.IdentityReference.Value -eq $builtInAdministrators) -and ($_.AccessControlType -eq $allow) })[0]
|
||
|
if (!$dirAccess) {
|
||
|
throw "Unexpected, administators do not have an allowed rule on the $serviceName installed directory"
|
||
|
}
|
||
|
$newAccess = New-Object System.Security.AccessControl.FileSystemAccessRule -ArgumentList $dirAccess.IdentityReference, ($dirAccess.FileSystemRights -bOr $modifyAcess), ($dirAccess.InheritanceFlags), ($dirAccess.PropagationFlags), $allow
|
||
|
$dirAcl.RemoveAccessRule($dirAccess)
|
||
|
$dirAcl.SetAccessRule($newAccess)
|
||
|
Set-Acl -Path $apiHome -AclObject $dirAcl | Out-Null
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$service = Get-WmiObject win32_service | Where-Object {$_.name -eq $serviceName}
|
||
|
if ($service) {
|
||
|
if ($service.StartInfo.EnvironmentVariables -and $service.StartInfo.EnvironmentVariables["USE_CURRENT_DIRECTORY_AS_ROOT"] -and $service.StartInfo.WorkingDirectory) {
|
||
|
$workingDirectory = $service.StartInfo.WorkingDirectory
|
||
|
} else {
|
||
|
$proc = Get-Process -id $service.ProcessId
|
||
|
$workingDirectory = GetIISAdminHome $proc
|
||
|
}
|
||
|
} else {
|
||
|
## dev-mode support, no restart can be perfomed
|
||
|
LogVerbose "Dev mode, scanning processes for IIS Admin API"
|
||
|
$devMode = $true
|
||
|
$workingDirectory = GetIISAdminHome (Get-Process -ProcessName dotnet)
|
||
|
}
|
||
|
|
||
|
$configLocation = [System.IO.Path]::Combine($workingDirectory, "config", "appsettings.json")
|
||
|
if ($devMode -and !(Test-Path $configLocation)) {
|
||
|
$configLocation = [System.IO.Path]::Combine($workingDirectory, "..", "..", "..", "config", "appsettings.json")
|
||
|
}
|
||
|
LogVerbose "Config Location $configLocation"
|
||
|
|
||
|
$jqExe = EnsureJQ
|
||
|
EnsureAcl $workingDirectory
|
||
|
$newContent = (Get-Content -Raw $configLocation | & $jqExe $query) -join "`n"
|
||
|
if (!(ConvertFrom-Json $newContent)) {
|
||
|
throw "Invalid query string"
|
||
|
}
|
||
|
LogVerbose $newContent
|
||
|
$newContent -join "`n" | Out-File -Force $configLocation
|
||
|
|
||
|
Restart-Service -Name $serviceName -Confirm:(!$quiet)
|
||
|
|
||
|
if ($wait) {
|
||
|
$retryCount = 10
|
||
|
$retryPeriod = 10
|
||
|
$started = $false
|
||
|
while (!$started -and ($retryCount -gt 0)) {
|
||
|
if ((Get-Service $serviceName).Status -eq [System.ServiceProcess.ServiceControllerStatus]::Running) {
|
||
|
$started = $true
|
||
|
} else {
|
||
|
Start-Sleep $retryPeriod
|
||
|
$retryCount--
|
||
|
}
|
||
|
}
|
||
|
if (!$started) {
|
||
|
throw "Timeout waiting for $serviceName to start"
|
||
|
}
|
||
|
}
|