checkpoint
This commit is contained in:
Родитель
ce6f23fac8
Коммит
7b3bc789f1
37
build.ps1
37
build.ps1
|
@ -87,23 +87,52 @@ function CleanUp() {
|
|||
}
|
||||
|
||||
function StartTest() {
|
||||
$group = GetGlobalVariable IIS_ADMIN_API_OWNERS
|
||||
$member = & ([System.IO.Path]::Combine($scriptDir, "setup", "security.ps1")) CurrentAdUser
|
||||
if (!(Get-LocalGroupMember -Group $group -Member $member -ErrorAction SilentlyContinue)) {
|
||||
Add-LocalGroupMember -Group $group -Member $member
|
||||
}
|
||||
$pingEndpoint = "https://localhost:$testPort"
|
||||
try {
|
||||
Invoke-WebRequest $pingEndpoint | Out-Null
|
||||
Invoke-WebRequest -UseDefaultCredentials -UseBasicParsing $pingEndpoint | Out-Null
|
||||
} catch {
|
||||
Write-Error "Failed to ping test server $pingEndpoint, did you forget to start it manually?"
|
||||
Exit 1
|
||||
}
|
||||
## do the real test
|
||||
}
|
||||
|
||||
function VerifyPath($path) {
|
||||
if (!(Test-Path $path)) {
|
||||
Write-Path "$path does not exist"
|
||||
return $false
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
function VerifyPrecondition() {
|
||||
if (!(VerifyPath [System.IO.Path]::Combine($projectRoot, "test", "appsettings.test.json")) `
|
||||
-or !(VerifyPath [System.IO.Path]::Combine($projectRoot, "test", "Microsoft.IIS.Administration.Tests", "test.config.json.template"))) {
|
||||
throw "Test configurations do no exist, run .\scripts\Configure-DevEnvironment.ps1 -ConfigureTestEnvironment"
|
||||
}
|
||||
}
|
||||
|
||||
function GetGlobalVariable($name) {
|
||||
& ([System.IO.Path]::Combine($scriptDir, "setup", "globals.ps1")) $name
|
||||
}
|
||||
|
||||
########################################################### Main Script ##################################################################
|
||||
|
||||
$scriptDir = Join-Path $PSScriptRoot "scripts"
|
||||
try {
|
||||
$projectRoot = git rev-parse --show-toplevel
|
||||
} catch {
|
||||
Write-Warning "Error looking for project root $_, using script location instead"
|
||||
$projectRoot = $PSScriptRoot
|
||||
}
|
||||
$scriptDir = Join-Path $projectRoot "scripts"
|
||||
# publish script only takes full path
|
||||
$publishPath = ForceResolvePath "$publishPath"
|
||||
$installPath = ForceResolvePath "$installPath"
|
||||
$serviceName = & ([System.IO.Path]::Combine($scriptDir, "setup", "globals.ps1")) DEFAULT_SERVICE_NAME
|
||||
$serviceName = GetGlobalVariable DEFAULT_SERVICE_NAME
|
||||
|
||||
Write-Host "[Build] Starting clean up..."
|
||||
CleanUp
|
||||
|
|
|
@ -252,9 +252,9 @@ function Set-Acls($_path) {
|
|||
[System.Security.AccessControl.PropagationFlags]::None,
|
||||
[System.Security.AccessControl.AccessControlType]::Allow)
|
||||
|
||||
$administratorsModify = New-Object System.Security.AccessControl.FileSystemAccessRule(
|
||||
$administratorsRead = New-Object System.Security.AccessControl.FileSystemAccessRule(
|
||||
$administrators,
|
||||
[System.Security.AccessControl.FileSystemRights]::Modify,
|
||||
[System.Security.AccessControl.FileSystemRights]::ReadAndExecute,
|
||||
[System.Security.AccessControl.InheritanceFlags]"ContainerInherit,ObjectInherit",
|
||||
[System.Security.AccessControl.PropagationFlags]::None,
|
||||
[System.Security.AccessControl.AccessControlType]::Allow)
|
||||
|
@ -281,7 +281,7 @@ function Set-Acls($_path) {
|
|||
$acl.Access | Foreach-Object { $acl.RemoveAccessRule($_) }
|
||||
$acl.AddAccessRule($currentUserRead)
|
||||
$acl.AddAccessRule($trustedInstallerFullControl)
|
||||
$acl.AddAccessRule($administratorsModify)
|
||||
$acl.AddAccessRule($administratorsRead)
|
||||
$acl.AddAccessRule($systemRead)
|
||||
# Update the folder to use the new ACL
|
||||
Set-Acl -Path $_path -AclObject $acl
|
||||
|
@ -293,7 +293,7 @@ function Set-Acls($_path) {
|
|||
# Remove all existing access rules
|
||||
$acl.Access | Foreach-Object { $acl.RemoveAccessRule($_) }
|
||||
$acl.AddAccessRule($currentUserRead)
|
||||
$acl.AddAccessRule($administratorsModify)
|
||||
$acl.AddAccessRule($administratorsRead)
|
||||
$acl.AddAccessRule($trustedInstallerFullControl)
|
||||
$acl.AddAccessRule($systemFullControl)
|
||||
# Update the folder to use the new ACL
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
<AssemblyName>Microsoft.IIS.Administration</AssemblyName>
|
||||
<OutputType>Exe</OutputType>
|
||||
<PackageId>Microsoft.IIS.Administration</PackageId>
|
||||
<IsTransformWebConfigDisabled>true</IsTransformWebConfigDisabled>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -41,6 +42,7 @@
|
|||
<PackageReference Include="Microsoft.AspNetCore.Antiforgery" Version="2.1.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication" Version="2.1.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.1.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting.WindowsServices" Version="2.1.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.1.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.HttpSys" Version="2.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.1.1" />
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
namespace Microsoft.IIS.Administration {
|
||||
using AspNetCore.Builder;
|
||||
using AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Hosting.WindowsServices;
|
||||
using Microsoft.AspNetCore.Server.HttpSys;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.IIS.Administration.WindowsService;
|
||||
using Serilog;
|
||||
|
||||
public class Program {
|
||||
|
@ -45,8 +45,7 @@ namespace Microsoft.IIS.Administration {
|
|||
//
|
||||
// Run as a Service
|
||||
Log.Information($"Running as service: {serviceName}");
|
||||
new ServiceHelper(serviceName).Run(token => host.RunAsync(token))
|
||||
.Wait();
|
||||
host.RunAsService();
|
||||
}
|
||||
else {
|
||||
//
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
|
||||
namespace Microsoft.IIS.Administration.WindowsService {
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
|
||||
static class Interop {
|
||||
private const string SERVICE_CORE_API_SET = "api-ms-win-service-core-l1-1-0";
|
||||
|
||||
public const int SERVICE_TYPE_WIN32_OWN_PROCESS = 0x00000010;
|
||||
public const int SERVICE_TYPE_WIN32_SHARE_PROCESS = 0x00000020;
|
||||
public const int SERVICE_TYPE_WIN32 = SERVICE_TYPE_WIN32_OWN_PROCESS | SERVICE_TYPE_WIN32_SHARE_PROCESS;
|
||||
|
||||
public const int SERVICE_CONTROL_STOP = 0x00000001;
|
||||
public const int SERVICE_ACCEPT_STOP = 0x00000001;
|
||||
|
||||
public const int SERVICE_STOPPED = 0x00000001;
|
||||
public const int SERVICE_START_PENDING = 0x00000002;
|
||||
public const int SERVICE_STOP_PENDING = 0x00000003;
|
||||
public const int SERVICE_RUNNING = 0x00000004;
|
||||
public const int SERVICE_CONTINUE_PENDING = 0x00000005;
|
||||
public const int SERVICE_PAUSE_PENDING = 0x00000006;
|
||||
public const int SERVICE_PAUSED = 0x00000007;
|
||||
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct SERVICE_STATUS {
|
||||
public int serviceType;
|
||||
public int currentState;
|
||||
public int controlsAccepted;
|
||||
public int win32ExitCode;
|
||||
public int serviceSpecificExitCode;
|
||||
public int checkPoint;
|
||||
public int waitHint;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public class SERVICE_TABLE_ENTRY {
|
||||
public IntPtr name;
|
||||
public Delegate callback;
|
||||
}
|
||||
|
||||
[DllImport(SERVICE_CORE_API_SET, CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
public extern static IntPtr RegisterServiceCtrlHandlerExW(string serviceName, Delegate callback, IntPtr userData);
|
||||
|
||||
[DllImport(SERVICE_CORE_API_SET, CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
public extern static bool SetServiceStatus(IntPtr serviceHandle, ref SERVICE_STATUS status);
|
||||
|
||||
[DllImport(SERVICE_CORE_API_SET, CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
public extern static bool StartServiceCtrlDispatcherW(IntPtr entry);
|
||||
}
|
||||
}
|
|
@ -1,215 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
|
||||
namespace Microsoft.IIS.Administration.WindowsService {
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
sealed class ServiceHelper {
|
||||
private const uint ERROR_MORE_DATA = 0xEA;
|
||||
private const int PENDING_TIMEOUT = 4000; // Timeout in ms during starting/stopping
|
||||
|
||||
private delegate int SvcCtrlHandlerEx(int control, int eventType, IntPtr eventData, IntPtr eventContext);
|
||||
private delegate void SvcMainHandler(int argCount, IntPtr args);
|
||||
|
||||
private string _serviceName;
|
||||
private Task _svcInitTask;
|
||||
private CancellationTokenSource _cancellationToken = new CancellationTokenSource();
|
||||
private IntPtr _serviceHandle = IntPtr.Zero;
|
||||
|
||||
SvcMainHandler _svcMainHandler;
|
||||
SvcCtrlHandlerEx _svcCtrlHandlerEx;
|
||||
|
||||
class TaskState {
|
||||
public ExceptionDispatchInfo Exception { get; set; }
|
||||
}
|
||||
|
||||
public ServiceHelper(string serviceName) {
|
||||
if (string.IsNullOrEmpty(serviceName)) {
|
||||
throw new ArgumentNullException(nameof(serviceName));
|
||||
}
|
||||
|
||||
_serviceName = serviceName;
|
||||
_svcMainHandler = new SvcMainHandler(SvcMain);
|
||||
_svcCtrlHandlerEx = new SvcCtrlHandlerEx(SvcCtrlHandler);
|
||||
}
|
||||
|
||||
private bool IsService {
|
||||
get {
|
||||
return !string.IsNullOrEmpty(_serviceName);
|
||||
}
|
||||
}
|
||||
|
||||
public string ServiceName {
|
||||
get {
|
||||
return _serviceName;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Run(Action<CancellationToken> action) {
|
||||
if (action == null) {
|
||||
throw new ArgumentNullException(nameof(action));
|
||||
}
|
||||
|
||||
if (!IsService) {
|
||||
throw new InvalidOperationException("The process is not running as Windows Service");
|
||||
}
|
||||
|
||||
await EnsureInit();
|
||||
|
||||
await Task.Run(()=> action(_cancellationToken.Token));
|
||||
}
|
||||
|
||||
private Task EnsureInit() {
|
||||
if (_svcInitTask != null) {
|
||||
return _svcInitTask;
|
||||
}
|
||||
|
||||
_svcInitTask = CreateInitTask();
|
||||
|
||||
//
|
||||
// Start StartServiceCtrlDispatcher
|
||||
Task.Run(() => {
|
||||
IntPtr namePtr = Marshal.StringToHGlobalUni(ServiceName);
|
||||
|
||||
try {
|
||||
//
|
||||
// Build SERVICE_TABLE_ENTRY[2] table
|
||||
IntPtr ptr = Marshal.AllocHGlobal(2 * Marshal.SizeOf<Interop.SERVICE_TABLE_ENTRY>());
|
||||
|
||||
Marshal.StructureToPtr(new Interop.SERVICE_TABLE_ENTRY() { callback = _svcMainHandler, name = namePtr }, ptr, true);
|
||||
Marshal.StructureToPtr(new Interop.SERVICE_TABLE_ENTRY() { callback = null, name = IntPtr.Zero },
|
||||
new IntPtr(ptr.ToInt64() + Marshal.SizeOf<Interop.SERVICE_TABLE_ENTRY>()), true);
|
||||
|
||||
//
|
||||
// Blocks until the Windows Service stops
|
||||
if (!Interop.StartServiceCtrlDispatcherW(ptr)) {
|
||||
throw new Win32Exception();
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
CompleteInitTask(e);
|
||||
throw e;
|
||||
}
|
||||
finally {
|
||||
Marshal.FreeHGlobal(namePtr);
|
||||
}
|
||||
});
|
||||
|
||||
return _svcInitTask;
|
||||
}
|
||||
|
||||
|
||||
private void SvcMain(int argCount, IntPtr args) {
|
||||
try {
|
||||
//
|
||||
// Register the handler function for the service
|
||||
_serviceHandle = Interop.RegisterServiceCtrlHandlerExW(ServiceName, _svcCtrlHandlerEx, IntPtr.Zero);
|
||||
if (_serviceHandle == IntPtr.Zero) {
|
||||
throw new Win32Exception();
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// Report Starting
|
||||
SetStatus(Interop.SERVICE_START_PENDING);
|
||||
|
||||
|
||||
//
|
||||
// Do some startup logic here...
|
||||
//
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
// Report Running
|
||||
SetStatus(Interop.SERVICE_RUNNING);
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
CompleteInitTask(null);
|
||||
|
||||
|
||||
//
|
||||
// Wait for stop event
|
||||
_cancellationToken.Token.WaitHandle.WaitOne();
|
||||
}
|
||||
catch (Exception e) {
|
||||
CompleteInitTask(e);
|
||||
throw e;
|
||||
}
|
||||
finally {
|
||||
//
|
||||
// Report Stopped
|
||||
SetStatus(Interop.SERVICE_STOPPED);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private int SvcCtrlHandler(int command, int eventType, IntPtr eventData, IntPtr eventContext) {
|
||||
//
|
||||
// Handle service control operation
|
||||
switch (command) {
|
||||
case Interop.SERVICE_CONTROL_STOP:
|
||||
SetStatus(Interop.SERVICE_STOP_PENDING);
|
||||
|
||||
//
|
||||
// Signal the service to stop
|
||||
_cancellationToken.Cancel();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void SetStatus(int state, int error = 0) {
|
||||
if (_serviceHandle == IntPtr.Zero) {
|
||||
throw new ArgumentNullException(nameof(_serviceHandle));
|
||||
}
|
||||
|
||||
var status = new Interop.SERVICE_STATUS() {
|
||||
currentState = state,
|
||||
win32ExitCode = error,
|
||||
serviceType = Interop.SERVICE_TYPE_WIN32_OWN_PROCESS,
|
||||
controlsAccepted = (state == Interop.SERVICE_START_PENDING) ? 0 : Interop.SERVICE_ACCEPT_STOP,
|
||||
waitHint = (state == Interop.SERVICE_START_PENDING) || (state == Interop.SERVICE_STOP_PENDING) ? PENDING_TIMEOUT : 0
|
||||
};
|
||||
|
||||
Interop.SetServiceStatus(_serviceHandle, ref status); // Ignore errors
|
||||
}
|
||||
|
||||
private Task CreateInitTask() {
|
||||
return new Task(s => {
|
||||
var state = (TaskState)s;
|
||||
|
||||
if (state.Exception != null) {
|
||||
state.Exception.Throw();
|
||||
}
|
||||
},
|
||||
new TaskState());
|
||||
}
|
||||
|
||||
private void CompleteInitTask(Exception e) {
|
||||
if (_svcInitTask.Status != TaskStatus.Created) {
|
||||
return; // The task has been started already
|
||||
}
|
||||
|
||||
//
|
||||
// Fail the task
|
||||
if (e != null) {
|
||||
((TaskState)_svcInitTask.AsyncState).Exception = ExceptionDispatchInfo.Capture(e);
|
||||
}
|
||||
|
||||
_svcInitTask.Start();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
{
|
||||
"host_id": "",
|
||||
"host_name": "IIS Administration API",
|
||||
"urls": "https://*:44326",
|
||||
"security": {
|
||||
"require_windows_authentication": true,
|
||||
"users": {
|
||||
"administrators": [
|
||||
"IIS Administrators",
|
||||
"IIS Administration API Owners"
|
||||
],
|
||||
"owners": [
|
||||
"IIS Administration API Owners"
|
||||
]
|
||||
},
|
||||
"access_policy": {
|
||||
"api": {
|
||||
"users": "administrators",
|
||||
"access_key": true
|
||||
},
|
||||
"api_keys": {
|
||||
"users": "administrators",
|
||||
"access_key": false
|
||||
},
|
||||
"system": {
|
||||
"users": "owners",
|
||||
"access_key": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"logging": {
|
||||
"enabled": true,
|
||||
"file_name": "iis-admin-api-test-{Date}.txt",
|
||||
"min_level": "Information",
|
||||
"path": "%temp%\\iis-admin-api\\"
|
||||
},
|
||||
"auditing": {
|
||||
"enabled": true,
|
||||
"file_name": "audit-{Date}.txt",
|
||||
"path": null
|
||||
},
|
||||
"cors": {
|
||||
"rules": [
|
||||
{
|
||||
"origin": "https://manage.iis.net",
|
||||
"allow": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"files": {
|
||||
"locations": [
|
||||
{
|
||||
"alias": "inetpub",
|
||||
"path": "%systemdrive%\\inetpub",
|
||||
"claims": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
{
|
||||
"alias": "tests",
|
||||
"path": "D:\\GitHub\\Microsoft\\IIS.Administration\\.test",
|
||||
"claims": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче