[TFS 2017] 實作 Build vNext 自動部署 Windows Service

多年前的一篇 https://dotblogs.com.tw/yc421206/archive/2009/03/24/7675.aspx 有寫到如何利用 InstallUtil 安裝 Windows Service,這次剛好碰到 Windows Service 專案,把部署過程自動化也是剛好而已

環境

  • Windows Server 2016
  • SQL Server 2016 Developer Edition
  • Team Foundation Server 2017 Update 1

本文連結


InstallUtil 手動安裝腳本

installer.bat

@echo off
REM The following directory is for .NET 4.0
set dotNetFX4=%SystemRoot%\Microsoft.NET\Framework\v4.0.30319
set installUtil=%dotNetFX4%\InstallUtil.exe
set batchFolder=%~dp0
echo InstallUtil:%installUtil%
echo BatchFolder:%batchFolder%
::pause
::set PATH=%PATH%;%dotNetFX4%
set serviceName=BLL.WinService.exe
set servicePatch=%batchFolder%%serviceName%
echo Service:%servicePatch%
echo Installing %serviceName%...
echo ---------------------------------------------------
%installUtil% /i %servicePatch%
echo ---------------------------------------------------
echo Done.
pause

uninstaller.bat

@echo off
REM The following directory is for .NET 4.0
set dotNetFX4=%SystemRoot%\Microsoft.NET\Framework\v4.0.30319
set installUtil=%dotNetFX4%\InstallUtil.exe
set batchFolder=%~dp0
echo InstallUtil:%installUtil%
echo BatchFolder:%batchFolder%
::pause
::set PATH=%PATH%;%dotNetFX4%
set serviceName=BLL.WinService.exe
set servicePatch=%batchFolder%%serviceName%
echo Service:%servicePatch%
echo Uninstalling %serviceName%...
echo ---------------------------------------------------
%installUtil% /u %servicePatch%
echo ---------------------------------------------------
echo Done.
pause
把它們加入專案裡面,跟著服務一起輸出,調用腳本時記得使用管理員權限

 

SC 手動安裝腳本

除了 InstallUtil.exe 之外,sc.exe 也能部署 Windows Service,以下連結用 batch 寫了相當然完整的部署腳本分別是 safeServiceDelete.bat、safeServiceStop.bat、safeServiceStop.bat

https://technet.microsoft.com/en-us/library/cc990289(v=ws.11).aspx

由於沒有 Create Service,所以就自己寫了一個 SafeCreateService.bat,腳本內容如下:

@echo off
::START "runas /user:remote computer\administrator /password:ClientS00" uninstaller-1.bat /K
IF [%1]==[] GOTO usage
IF [%2]==[] GOTO usage
IF [%3]==[] GOTO usage
IF [%4]==[] GOTO usage
IF [%5]==[] GOTO usage
IF NOT "%1"=="" SET serviceName=%1
IF NOT "%2"=="" SET binpath=%2
IF NOT "%3"=="" SET serviceLogonId=%3
IF NOT "%4"=="" SET serviceLogonPassword=%4
IF NOT "%5"=="" SET serverName=%5
SC %serverName% query %serviceName%
IF errorlevel 1060 GOTO ServiceNotFound
IF errorlevel 1722 GOTO SystemOffline
IF errorlevel 1001 GOTO DeletingServiceDelay
:ResolveInitialState
SC %serverName% query %serviceName% | FIND "STATE" | FIND "RUNNING"
IF errorlevel 0 IF NOT errorlevel 1 GOTO StopService
SC %serverName% query %serviceName% | FIND "STATE" | FIND "STOPPED"
IF errorlevel 0 IF NOT errorlevel 1 GOTO StoppedService
SC %serverName% query %serviceName% | FIND "STATE" | FIND "PAUSED"
IF errorlevel 0 IF NOT errorlevel 1 GOTO SystemOffline
echo Service State is changing, waiting for service to resolve its state before making changes
sc %serverName% query %serviceName% | Find "STATE"
ping -n 2 127.0.0.1 > NUL
GOTO ResolveInitialState
:StopService
echo Stopping %serviceName% on %serverName%
sc %serverName% stop %serviceName%
GOTO StoppingService
:StoppingServiceDelay
echo Waiting for %serviceName% to stop
ping -n 2 127.0.0.1 > NUL
:StoppingService
SC %serverName% query %serviceName% | FIND "STATE" | FIND "STOPPED"
IF errorlevel 1 GOTO StoppingServiceDelay
:StoppedService
echo %serviceName% on %serverName% is stopped
GOTO DeleteService
:DeleteService
echo Deleting %serviceName% on %serverName%
SC %serverName% delete %serviceName%
:DeletingServiceDelay
echo Waiting for %serviceName% to get deleted
ping -n 2 127.0.0.1 > NUL
:DeletingService
SC %serverName% query %serviceName%
IF NOT errorlevel 1060 GOTO DeletingServiceDelay
:DeletedService
echo %serviceName% on %serverName% is deleted
GOTO CreateService
:SystemOffline
echo Server %serverName% is not accessible or is offline
GOTO End
:ServiceNotFound
echo Service %serviceName% is not installed on Server %serverName%
GOTO CreateService
:CreateService
echo Creating %serviceName% on %serverName%
SC %serverName% create %serviceName% binpath=%binpath%
SC %serverName% config %serviceName% obj= %serviceLogonId% password= %serviceLogonPassword%
SC %serverName% config %serviceName% type= share start= auto displayname= "測試服務"
SC %serverName% description %serviceName% "測試服務"
:CreatingServiceDelay
echo Waiting for %serviceName% to get created
ping -n 2 127.0.0.1 > NUL
:CreatingService
::SC %serverName% query %serviceName% >NUL
SC %serverName% query %serviceName% | FIND "STATE" | FIND "STOPPED"
IF errorlevel 1 GOTO CreatingServiceDelay
:CreatedService
echo %serviceName% on %serverName% is created
GOTO End
:usage
echo Will cause a local/remote service to START (if not already started).
echo This script will waiting for the service to enter the started state if necessary.
echo.
echo %0 [service name] [system name]
echo Example: %0 MyService server1
echo Example: %0 MyService (for local PC)
echo.
::GOTO:eof
:End
::pause

 

上面的腳本基本上就能讓我們管理服務,我另外寫了一個 test.bat 腳本來調用它們
@echo off
set serviceName=MqReceiverService
set binpath=C:\Service1.exe
set serviceLogonId=.\Administrator
set serviceLogonPassword=password
set serverName=\\remote computer

Call SafeDeleteService %serviceName% %serverName%
Call SafeCreateService %serviceName% %binpath% %serviceLogonId% %serviceLogonPassword% %serverName%
Call SafeStartService %serviceName% %serverName%

 

如何通過 TFS 自動部署 Windows Service

Builds:
這裡沒甚麼特別的,主要是把專案內的腳本複製到 $(build.artifactstagingdirectory)
 
Release:
確定手動部署可以執行後再來設定自動部署,以下幾點要設定:
  1. 目標電腦的 Windows Service 目錄要開啟分享
  2. Build Agent 所啟動的服務要加入目標電腦的管理員群組
用 TFS 調用腳本,設定如下圖:
腳本很簡單,但花了很多時間在處理遠端電腦權限的問題

若有謬誤,煩請告知,新手發帖請多包涵


Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET

Image result for microsoft+mvp+logo