运行 Windows 服务器上的后台 Solr 6.0.1
Run Solr 6.0.1 in background on Windows server
我想在Windows Server 2012 R2 上启动一个solr core(版本6.0.1)(命令是:bin\solr.cmd 启动),并保持它运行注销后。我还想 运行 它在我自己以外的帐户下,我希望它自动启动。我遇到过这个 post、How to run solr on a windows server so it starts up automatically?,但那是 6 年前的事了,所以我认为新版本可能会有新的方法。另外,我的新版本不是 运行ning on Tomcat。它充当自己的服务器,这是与示例中版本的另一个区别。谢谢!
您可以使用 NSSM to register Solr as a Windows Service.
"c:\Program Files\nssm\win64\nssm" install solr6
在前台使用 -f
参数到 运行 Solr 很重要(在本例中意味着 "run it inside NSSM")。这可以在为 NSSM ("Arguments") 弹出的框中更改。您还可以选择 运行 服务的用户 "Log on".
无需任何外部组件即可在 PowerShell 中完成。这就是我目前使用的 Solr 7.5.0 来安装和 运行 作为本机 Windows 服务。我有理由相信这将适用于在 Server 2012 R2.
上使用 bin\solr.cmd 的任何版本
您需要创建一个名为 SOLR_HOME 的环境变量,并将值设置为您的 Solr \bin 目录的完整路径,并可能重新启动以获取该新环境变量。进行任何必要的更改,尤其是 #region Solr configuration
下的内存和端口。然后将下面的代码放入 Solr 根目录中名为 PSSolrService.ps1 的文件中,在 PowerShell 控制台中 cd 到该目录,然后 运行 安装命令: ./PSService.ps1 -Setup
这应该给你一个名为 Solr 的 Windows 服务,它在系统启动时自动设置为 运行。然后,您还可以使用 NET STOP Solr
和 NET START Solr
停止和启动该服务,就像任何其他 Windows 服务一样。
#
# PSSolrService.ps1
#
#Requires -version 2
<#
.SYNOPSIS
A simple Windows service, in a standalone PowerShell script.
.DESCRIPTION
This script demonstrates how to write a Windows service in pure PowerShell.
It dynamically generates a small PSService.exe wrapper, that in turn
invokes this PowerShell script again for its start and stop events.
.PARAMETER Start
Start the service.
.PARAMETER Stop
Stop the service.
.PARAMETER Restart
Stop then restart the service.
.PARAMETER Status
Get the current service status: Not installed / Stopped / Running
.PARAMETER Setup
Install the service.
.PARAMETER Remove
Uninstall the service.
.PARAMETER Service
Run the service in the background. Used internally by the script.
Do not use, except for test purposes.
.PARAMETER Control
Send a control message to the service thread.
.PARAMETER Version
Display this script version and exit.
.EXAMPLE
# Setup the service and run it for the first time
C:\PS>.\PSService.ps1 -Status
Not installed
C:\PS>.\PSService.ps1 -Setup
C:\PS># At this stage, a copy of PSService.ps1 is present in the path
C:\PS>PSService -Status
Stopped
C:\PS>PSService -Start
C:\PS>PSService -Status
Running
C:\PS># Load the log file in Notepad.exe for review
C:\PS>notepad ${ENV:windir}\Logs\PSService.log
.EXAMPLE
# Stop the service and uninstall it.
C:\PS>PSService -Stop
C:\PS>PSService -Status
Stopped
C:\PS>PSService -Remove
C:\PS># At this stage, no copy of PSService.ps1 is present in the path anymore
C:\PS>.\PSService.ps1 -Status
Not installed
.EXAMPLE
# Send a control message to the service, and verify that it received it.
C:\PS>PSService -Control Hello
C:\PS>Notepad C:\Windows\Logs\PSService.log
# The last lines should contain a trace of the reception of this Hello message
#>
[CmdletBinding(DefaultParameterSetName='Status')]
Param(
[Parameter(ParameterSetName='Start', Mandatory=$true)]
[Switch]$Start, # Start the service
[Parameter(ParameterSetName='Stop', Mandatory=$true)]
[Switch]$Stop, # Stop the service
[Parameter(ParameterSetName='Restart', Mandatory=$true)]
[Switch]$Restart, # Restart the service
[Parameter(ParameterSetName='Status', Mandatory=$false)]
[Switch]$Status = $($PSCmdlet.ParameterSetName -eq 'Status'), # Get the current service status
[Parameter(ParameterSetName='Setup', Mandatory=$true)]
[Switch]$Setup, # Install the service
[Parameter(ParameterSetName='Remove', Mandatory=$true)]
[Switch]$Remove, # Uninstall the service
[Parameter(ParameterSetName='Service', Mandatory=$true)]
[Switch]$Service, # Run the service
[Parameter(ParameterSetName='Control', Mandatory=$true)]
[String]$Control = $null, # Control message to send to the service
[Parameter(ParameterSetName='Version', Mandatory=$true)]
[Switch]$Version # Get this script version
)
# modify this to update script version used by Solr Service
$scriptVersion = "2018-11-07"
if( -not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator"))
{
Write-Warning -Message "Please run thisscript with Administrator permissions."
return
}
#region Solr configuration
$solrPort = '8983'
$solrMemory = '12g' # eg. 1g 4g
$solrHome = [environment]::GetEnvironmentVariable("SOLR_HOME",[EnvironmentVariableTarget]::Machine)
$solrRoot = Split-Path -Path (Split-Path -Path $solrHome -Parent) -Parent
$sorlStartCmd = Join-Path -Path $solrRoot -ChildPath "bin\solr.cmd"
if( -not (Test-Path -Path $sorlStartCmd) )
{
Write-Warning "Solr.cmd not exist: $sorlStartCmd"
return
}
#endregion
# This script name, with various levels of details
$argv0 = Get-Item $MyInvocation.MyCommand.Definition
$script = $argv0.basename # Ex: PSSolrService
$scriptName = $argv0.name # Ex: PSSolrService.ps1
$scriptFullName = $argv0.fullname # Ex: C:\Temp\PSSolrService.ps1
# Global settings
$serviceName = $script # A one-word name used for net start commands
$serviceDisplayName = "Solr"
$ServiceDescription = "Solr"
$installDir = "${ENV:ProgramFiles}$serviceName" # Where to install the service files
#$installDir = "${ENV:windir}\System32" # Where to install the service files
$scriptCopy = "$installDir$scriptName"
$exeName = "$serviceName.exe"
$exeFullName = "$installDir$exeName"
$logDir = "${ENV:windir}\Logs" # Where to log the service messages
$logFile = "$logDir$serviceName.log"
$logName = "Application" # Event Log name (Unrelated to the logFile!)
# Note: The current implementation only supports "classic" (ie. XP-compatble) event logs.
# To support new style (Vista and later) "Applications and Services Logs" folder trees, it would
# be necessary to use the new *WinEvent commands instead of the XP-compatible *EventLog commands.
# Gotcha: If you change $logName to "NEWLOGNAME", make sure that the registry key below does not exist:
# HKLM\System\CurrentControlSet\services\eventlog\Application\NEWLOGNAME
# Else, New-EventLog will fail, saying the log NEWLOGNAME is already registered as a source,
# even though "Get-WinEvent -ListLog NEWLOGNAME" says this log does not exist!
# If the -Version switch is specified, display the script version and exit.
if ($Version) {
Write-Output $scriptVersion
return
}
Function Now {
Param (
[Switch]$ms, # Append milliseconds
[Switch]$ns # Append nanoseconds
)
$Date = Get-Date
$now = ""
$now += "{0:0000}-{1:00}-{2:00} " -f $Date.Year, $Date.Month, $Date.Day
$now += "{0:00}:{1:00}:{2:00}" -f $Date.Hour, $Date.Minute, $Date.Second
$nsSuffix = ""
if ($ns) {
if ("$($Date.TimeOfDay)" -match "\.\d\d\d\d\d\d") {
$now += $matches[0]
$ms = $false
} else {
$ms = $true
$nsSuffix = "000"
}
}
if ($ms) {
$now += ".{0:000}$nsSuffix" -f $Date.MilliSecond
}
return $now
}
Function Log () {
Param(
[Parameter(Mandatory=$false, ValueFromPipeline=$true, Position=0)]
[String]$string
)
if (!(Test-Path $logDir)) {
New-Item -ItemType directory -Path $logDir | Out-Null
}
if ($String.length) {
$string = "$(Now) $pid $userName $string"
}
$string | Out-File -Encoding ASCII -Append "$logFile"
}
$scriptCopyCname = $scriptCopy -replace "\", "\" # Double backslashes. (The first \ is a regexp with \ escaped; The second is a plain string.)
$source = @"
using System;
using System.ServiceProcess;
using System.Diagnostics;
using System.Runtime.InteropServices; // SET STATUS
using System.ComponentModel; // SET STATUS
public enum ServiceType : int { // SET STATUS [
SERVICE_WIN32_OWN_PROCESS = 0x00000010,
SERVICE_WIN32_SHARE_PROCESS = 0x00000020,
}; // SET STATUS ]
public enum ServiceState : int { // SET STATUS [
SERVICE_STOPPED = 0x00000001,
SERVICE_START_PENDING = 0x00000002,
SERVICE_STOP_PENDING = 0x00000003,
SERVICE_RUNNING = 0x00000004,
SERVICE_CONTINUE_PENDING = 0x00000005,
SERVICE_PAUSE_PENDING = 0x00000006,
SERVICE_PAUSED = 0x00000007,
}; // SET STATUS ]
[StructLayout(LayoutKind.Sequential)] // SET STATUS [
public struct ServiceStatus {
public ServiceType dwServiceType;
public ServiceState dwCurrentState;
public int dwControlsAccepted;
public int dwWin32ExitCode;
public int dwServiceSpecificExitCode;
public int dwCheckPoint;
public int dwWaitHint;
}; // SET STATUS ]
public enum Win32Error : int { // WIN32 errors that we may need to use
NO_ERROR = 0,
ERROR_APP_INIT_FAILURE = 575,
ERROR_FATAL_APP_EXIT = 713,
ERROR_SERVICE_NOT_ACTIVE = 1062,
ERROR_EXCEPTION_IN_SERVICE = 1064,
ERROR_SERVICE_SPECIFIC_ERROR = 1066,
ERROR_PROCESS_ABORTED = 1067,
};
public class Service_$serviceName : ServiceBase { // $serviceName may begin with a digit; The class name must begin with a letter
private System.Diagnostics.EventLog eventLog; // EVENT LOG
private ServiceStatus serviceStatus; // SET STATUS
public Service_$serviceName() {
ServiceName = "$serviceName";
CanStop = true;
CanPauseAndContinue = false;
AutoLog = true;
eventLog = new System.Diagnostics.EventLog(); // EVENT LOG [
if (!System.Diagnostics.EventLog.SourceExists(ServiceName)) {
System.Diagnostics.EventLog.CreateEventSource(ServiceName, "$logName");
}
eventLog.Source = ServiceName;
eventLog.Log = "$logName"; // EVENT LOG ]
EventLog.WriteEntry(ServiceName, "$exeName $serviceName()"); // EVENT LOG
}
[DllImport("advapi32.dll", SetLastError=true)] // SET STATUS
private static extern bool SetServiceStatus(IntPtr handle, ref ServiceStatus serviceStatus);
protected override void OnStart(string [] args) {
EventLog.WriteEntry(ServiceName, "$exeName OnStart() // Entry. Starting script '$scriptCopyCname' -Start"); // EVENT LOG
// Set the service state to Start Pending. // SET STATUS [
// Only useful if the startup time is long. Not really necessary here for a 2s startup time.
serviceStatus.dwServiceType = ServiceType.SERVICE_WIN32_OWN_PROCESS;
serviceStatus.dwCurrentState = ServiceState.SERVICE_START_PENDING;
serviceStatus.dwWin32ExitCode = 0;
serviceStatus.dwWaitHint = 2000; // It takes about 2 seconds to start PowerShell
SetServiceStatus(ServiceHandle, ref serviceStatus); // SET STATUS ]
// Start a child process with another copy of this script
try {
Process p = new Process();
// Redirect the output stream of the child process.
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.FileName = "PowerShell.exe";
p.StartInfo.Arguments = "-ExecutionPolicy Bypass -c & '$scriptCopyCname' -Start"; // Works if path has spaces, but not if it contains ' quotes.
p.Start();
// Read the output stream first and then wait. (To avoid deadlocks says Microsoft!)
string output = p.StandardOutput.ReadToEnd();
// Wait for the completion of the script startup code, that launches the -Service instance
p.WaitForExit();
if (p.ExitCode != 0) throw new Win32Exception((int)(Win32Error.ERROR_APP_INIT_FAILURE));
// Success. Set the service state to Running. // SET STATUS
serviceStatus.dwCurrentState = ServiceState.SERVICE_RUNNING; // SET STATUS
} catch (Exception e) {
EventLog.WriteEntry(ServiceName, "$exeName OnStart() // Failed to start $scriptCopyCname. " + e.Message, EventLogEntryType.Error); // EVENT LOG
// Change the service state back to Stopped. // SET STATUS [
serviceStatus.dwCurrentState = ServiceState.SERVICE_STOPPED;
Win32Exception w32ex = e as Win32Exception; // Try getting the WIN32 error code
if (w32ex == null) { // Not a Win32 exception, but maybe the inner one is...
w32ex = e.InnerException as Win32Exception;
}
if (w32ex != null) { // Report the actual WIN32 error
serviceStatus.dwWin32ExitCode = w32ex.NativeErrorCode;
} else { // Make up a reasonable reason
serviceStatus.dwWin32ExitCode = (int)(Win32Error.ERROR_APP_INIT_FAILURE);
} // SET STATUS ]
} finally {
serviceStatus.dwWaitHint = 0; // SET STATUS
SetServiceStatus(ServiceHandle, ref serviceStatus); // SET STATUS
EventLog.WriteEntry(ServiceName, "$exeName OnStart() // Exit"); // EVENT LOG
}
}
protected override void OnStop() {
EventLog.WriteEntry(ServiceName, "$exeName OnStop() // Entry"); // EVENT LOG
// Start a child process with another copy of ourselves
Process p = new Process();
// Redirect the output stream of the child process.
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.FileName = "PowerShell.exe";
p.StartInfo.Arguments = "-c & '$scriptCopyCname' -Stop"; // Works if path has spaces, but not if it contains ' quotes.
p.Start();
// Read the output stream first and then wait.
string output = p.StandardOutput.ReadToEnd();
// Wait for the PowerShell script to be fully stopped.
p.WaitForExit();
// Change the service state back to Stopped. // SET STATUS
serviceStatus.dwCurrentState = ServiceState.SERVICE_STOPPED; // SET STATUS
SetServiceStatus(ServiceHandle, ref serviceStatus); // SET STATUS
EventLog.WriteEntry(ServiceName, "$exeName OnStop() // Exit"); // EVENT LOG
}
public static void Main() {
System.ServiceProcess.ServiceBase.Run(new Service_$serviceName());
}
}
"@
# Check if we're running as a real user, or as the SYSTEM = As a service
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$userName = $identity.Name # Ex: "NT AUTHORITY\SYSTEM" or "Domain\Administrator"
$authority,$name = $username -split "\"
$isSystem = $identity.IsSystem # Do not test ($userName -eq "NT AUTHORITY\SYSTEM"), as this fails in non-English systems.
# Log "# `$userName = `"$userName`" ; `$isSystem = $isSystem"
if ($Setup) {Log ""} # Insert one blank line to separate test sessions logs
Log $MyInvocation.Line # The exact command line that was used to start us
# The following commands write to the event log, but we need to make sure the PSService source is defined.
New-EventLog -LogName $logName -Source $serviceName -ea SilentlyContinue
# Workaround for PowerShell v2 bug: $PSCmdlet Not yet defined in Param() block
$Status = ($PSCmdlet.ParameterSetName -eq 'Status')
if ($Start) { # Start the service
if ($isSystem) { # If running as SYSTEM, ie. invoked as a service
# Do whatever is necessary to start the service script instance
Log "$scriptName -Start: Starting script '$scriptFullName' -Service"
Write-EventLog -LogName $logName -Source $serviceName -EventId 1001 -EntryType Information -Message "$scriptName -Start: Starting script '$scriptFullName' -Service"
Start-Process PowerShell.exe -ArgumentList ("-c & '$scriptFullName' -Service")
} else {
Write-Verbose "Starting service $serviceName"
Write-EventLog -LogName $logName -Source $serviceName -EventId 1002 -EntryType Information -Message "$scriptName -Start: Starting service $serviceName"
Start-Service $serviceName # Ask Service Control Manager to start it
}
return
}
if ($Stop) { # Stop the service
if ($isSystem) { # If running as SYSTEM, ie. invoked as a service
# Do whatever is necessary to stop the service script instance
Write-EventLog -LogName $logName -Source $serviceName -EventId 1003 -EntryType Information -Message "$scriptName -Stop: Stopping script $scriptName -Service"
Log "$scriptName -Stop: Stopping script $scriptName -Service"
#region Solr stop
&$sorlStartCmd stop -p $solrPort
#endregion
} else {
Write-Verbose "Stopping service $serviceName"
Write-EventLog -LogName $logName -Source $serviceName -EventId 1004 -EntryType Information -Message "$scriptName -Stop: Stopping service $serviceName"
Stop-Service $serviceName # Ask Service Control Manager to stop it
}
return
}
if ($Restart) { # Restart the service
& $scriptFullName -Stop
& $scriptFullName -Start
return
}
if ($Status) { # Get the current service status
$spid = $null
$processes = @(Get-WmiObject Win32_Process -filter "Name = 'powershell.exe'" | Where-Object {
$_.CommandLine -match ".*$scriptCopyCname.*-Service"
})
foreach ($process in $processes) { # There should be just one, but be prepared for surprises.
$spid = $process.ProcessId
Write-Verbose "$serviceName Process ID = $spid"
}
# if (Test-Path "HKLM:\SYSTEM\CurrentControlSet\services$serviceName") {}
try {
$pss = Get-Service $serviceName -ea stop # Will error-out if not installed
} catch {
"Not Installed"
return
}
$pss.Status
if (($pss.Status -eq "Running") -and (!$spid)) { # This happened during the debugging phase
Write-Error "The Service Control Manager thinks $serviceName is started, but $serviceName.ps1 -Service is not running."
exit 1
}
return
}
if ($Setup) { # Install the service
# Check if it's necessary
try {
$pss = Get-Service $serviceName -ea stop # Will error-out if not installed
# Check if this script is newer than the installed copy.
if ((Get-Item $scriptCopy -ea SilentlyContinue).LastWriteTime -lt (Get-Item $scriptFullName -ea SilentlyContinue).LastWriteTime) {
Write-Verbose "Service $serviceName is already Installed, but requires upgrade"
& $scriptFullName -Remove
throw "continue"
} else {
Write-Verbose "Service $serviceName is already Installed, and up-to-date"
}
exit 0
} catch {
# This is the normal case here. Do not throw or write any error!
Write-Debug "Installation is necessary" # Also avoids a ScriptAnalyzer warning
# And continue with the installation.
}
if (!(Test-Path $installDir)) {
New-Item -ItemType directory -Path $installDir | Out-Null
}
# Copy the service script into the installation directory
if ($ScriptFullName -ne $scriptCopy) {
Write-Verbose "Installing $scriptCopy"
Copy-Item $ScriptFullName $scriptCopy
}
# Generate the service .EXE from the C# source embedded in this script
try {
Write-Verbose "Compiling $exeFullName"
Add-Type -TypeDefinition $source -Language CSharp -OutputAssembly $exeFullName -OutputType ConsoleApplication -ReferencedAssemblies "System.ServiceProcess" -Debug:$false
} catch {
$msg = $_.Exception.Message
Write-error "Failed to create the $exeFullName service stub. $msg"
exit 1
}
# Register the service
Write-Verbose "Registering service $serviceName"
$pss = New-Service $serviceName $exeFullName -DisplayName $serviceDisplayName -Description $ServiceDescription -StartupType Automatic
return
}
if ($Remove) { # Uninstall the service
# Check if it's necessary
try {
$pss = Get-Service $serviceName -ea stop # Will error-out if not installed
} catch {
Write-Verbose "Already uninstalled"
return
}
Stop-Service $serviceName # Make sure it's stopped
# In the absence of a Remove-Service applet, use sc.exe instead.
Write-Verbose "Removing service $serviceName"
$msg = sc.exe delete $serviceName
if ($LastExitCode) {
Write-Error "Failed to remove the service ${serviceName}: $msg"
exit 1
} else {
Write-Verbose $msg
}
# Remove the installed files
if (Test-Path $installDir) {
foreach ($ext in ("exe", "pdb", "ps1")) {
$file = "$installDir$serviceName.$ext"
if (Test-Path $file) {
Write-Verbose "Deleting file $file"
Remove-Item $file
}
}
if (!(@(Get-ChildItem $installDir -ea SilentlyContinue)).Count) {
Write-Verbose "Removing directory $installDir"
Remove-Item $installDir
}
}
return
}
if ($Service) { # Run the service
Write-EventLog -LogName $logName -Source $serviceName -EventId 1005 -EntryType Information -Message "$scriptName -Service # Beginning background job"
# Do the service background job
try
{
#region Solr start
Log "$scriptName Starting $sorlStartCmd with parameteres start -f -p $solrPort -m $solrMemory"
&$sorlStartCmd start -f -p $solrPort -m $solrMemory
#endregion
}
catch
{ # An exception occurred while runnning the service
$msg = $_.Exception.Message
$line = $_.InvocationInfo.ScriptLineNumber
Log "$scriptName -Service # Error at line ${line}: $msg"
}
finally
{
# Invoked in all cases: Exception or normally by -Stop
# Flush all leftover events (There may be some that arrived after we exited the while event loop, but before we unregistered the events)
$events = Get-Event | Remove-Event
# Log a termination event, no matter what the cause is.
Write-EventLog -LogName $logName -Source $serviceName -EventId 1006 -EntryType Information -Message "$script -Service # Exiting"
Log "$scriptName -Service # Exiting"
}
return
}
我想在Windows Server 2012 R2 上启动一个solr core(版本6.0.1)(命令是:bin\solr.cmd 启动),并保持它运行注销后。我还想 运行 它在我自己以外的帐户下,我希望它自动启动。我遇到过这个 post、How to run solr on a windows server so it starts up automatically?,但那是 6 年前的事了,所以我认为新版本可能会有新的方法。另外,我的新版本不是 运行ning on Tomcat。它充当自己的服务器,这是与示例中版本的另一个区别。谢谢!
您可以使用 NSSM to register Solr as a Windows Service.
"c:\Program Files\nssm\win64\nssm" install solr6
在前台使用 -f
参数到 运行 Solr 很重要(在本例中意味着 "run it inside NSSM")。这可以在为 NSSM ("Arguments") 弹出的框中更改。您还可以选择 运行 服务的用户 "Log on".
无需任何外部组件即可在 PowerShell 中完成。这就是我目前使用的 Solr 7.5.0 来安装和 运行 作为本机 Windows 服务。我有理由相信这将适用于在 Server 2012 R2.
上使用 bin\solr.cmd 的任何版本您需要创建一个名为 SOLR_HOME 的环境变量,并将值设置为您的 Solr \bin 目录的完整路径,并可能重新启动以获取该新环境变量。进行任何必要的更改,尤其是 #region Solr configuration
下的内存和端口。然后将下面的代码放入 Solr 根目录中名为 PSSolrService.ps1 的文件中,在 PowerShell 控制台中 cd 到该目录,然后 运行 安装命令: ./PSService.ps1 -Setup
这应该给你一个名为 Solr 的 Windows 服务,它在系统启动时自动设置为 运行。然后,您还可以使用 NET STOP Solr
和 NET START Solr
停止和启动该服务,就像任何其他 Windows 服务一样。
#
# PSSolrService.ps1
#
#Requires -version 2
<#
.SYNOPSIS
A simple Windows service, in a standalone PowerShell script.
.DESCRIPTION
This script demonstrates how to write a Windows service in pure PowerShell.
It dynamically generates a small PSService.exe wrapper, that in turn
invokes this PowerShell script again for its start and stop events.
.PARAMETER Start
Start the service.
.PARAMETER Stop
Stop the service.
.PARAMETER Restart
Stop then restart the service.
.PARAMETER Status
Get the current service status: Not installed / Stopped / Running
.PARAMETER Setup
Install the service.
.PARAMETER Remove
Uninstall the service.
.PARAMETER Service
Run the service in the background. Used internally by the script.
Do not use, except for test purposes.
.PARAMETER Control
Send a control message to the service thread.
.PARAMETER Version
Display this script version and exit.
.EXAMPLE
# Setup the service and run it for the first time
C:\PS>.\PSService.ps1 -Status
Not installed
C:\PS>.\PSService.ps1 -Setup
C:\PS># At this stage, a copy of PSService.ps1 is present in the path
C:\PS>PSService -Status
Stopped
C:\PS>PSService -Start
C:\PS>PSService -Status
Running
C:\PS># Load the log file in Notepad.exe for review
C:\PS>notepad ${ENV:windir}\Logs\PSService.log
.EXAMPLE
# Stop the service and uninstall it.
C:\PS>PSService -Stop
C:\PS>PSService -Status
Stopped
C:\PS>PSService -Remove
C:\PS># At this stage, no copy of PSService.ps1 is present in the path anymore
C:\PS>.\PSService.ps1 -Status
Not installed
.EXAMPLE
# Send a control message to the service, and verify that it received it.
C:\PS>PSService -Control Hello
C:\PS>Notepad C:\Windows\Logs\PSService.log
# The last lines should contain a trace of the reception of this Hello message
#>
[CmdletBinding(DefaultParameterSetName='Status')]
Param(
[Parameter(ParameterSetName='Start', Mandatory=$true)]
[Switch]$Start, # Start the service
[Parameter(ParameterSetName='Stop', Mandatory=$true)]
[Switch]$Stop, # Stop the service
[Parameter(ParameterSetName='Restart', Mandatory=$true)]
[Switch]$Restart, # Restart the service
[Parameter(ParameterSetName='Status', Mandatory=$false)]
[Switch]$Status = $($PSCmdlet.ParameterSetName -eq 'Status'), # Get the current service status
[Parameter(ParameterSetName='Setup', Mandatory=$true)]
[Switch]$Setup, # Install the service
[Parameter(ParameterSetName='Remove', Mandatory=$true)]
[Switch]$Remove, # Uninstall the service
[Parameter(ParameterSetName='Service', Mandatory=$true)]
[Switch]$Service, # Run the service
[Parameter(ParameterSetName='Control', Mandatory=$true)]
[String]$Control = $null, # Control message to send to the service
[Parameter(ParameterSetName='Version', Mandatory=$true)]
[Switch]$Version # Get this script version
)
# modify this to update script version used by Solr Service
$scriptVersion = "2018-11-07"
if( -not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator"))
{
Write-Warning -Message "Please run thisscript with Administrator permissions."
return
}
#region Solr configuration
$solrPort = '8983'
$solrMemory = '12g' # eg. 1g 4g
$solrHome = [environment]::GetEnvironmentVariable("SOLR_HOME",[EnvironmentVariableTarget]::Machine)
$solrRoot = Split-Path -Path (Split-Path -Path $solrHome -Parent) -Parent
$sorlStartCmd = Join-Path -Path $solrRoot -ChildPath "bin\solr.cmd"
if( -not (Test-Path -Path $sorlStartCmd) )
{
Write-Warning "Solr.cmd not exist: $sorlStartCmd"
return
}
#endregion
# This script name, with various levels of details
$argv0 = Get-Item $MyInvocation.MyCommand.Definition
$script = $argv0.basename # Ex: PSSolrService
$scriptName = $argv0.name # Ex: PSSolrService.ps1
$scriptFullName = $argv0.fullname # Ex: C:\Temp\PSSolrService.ps1
# Global settings
$serviceName = $script # A one-word name used for net start commands
$serviceDisplayName = "Solr"
$ServiceDescription = "Solr"
$installDir = "${ENV:ProgramFiles}$serviceName" # Where to install the service files
#$installDir = "${ENV:windir}\System32" # Where to install the service files
$scriptCopy = "$installDir$scriptName"
$exeName = "$serviceName.exe"
$exeFullName = "$installDir$exeName"
$logDir = "${ENV:windir}\Logs" # Where to log the service messages
$logFile = "$logDir$serviceName.log"
$logName = "Application" # Event Log name (Unrelated to the logFile!)
# Note: The current implementation only supports "classic" (ie. XP-compatble) event logs.
# To support new style (Vista and later) "Applications and Services Logs" folder trees, it would
# be necessary to use the new *WinEvent commands instead of the XP-compatible *EventLog commands.
# Gotcha: If you change $logName to "NEWLOGNAME", make sure that the registry key below does not exist:
# HKLM\System\CurrentControlSet\services\eventlog\Application\NEWLOGNAME
# Else, New-EventLog will fail, saying the log NEWLOGNAME is already registered as a source,
# even though "Get-WinEvent -ListLog NEWLOGNAME" says this log does not exist!
# If the -Version switch is specified, display the script version and exit.
if ($Version) {
Write-Output $scriptVersion
return
}
Function Now {
Param (
[Switch]$ms, # Append milliseconds
[Switch]$ns # Append nanoseconds
)
$Date = Get-Date
$now = ""
$now += "{0:0000}-{1:00}-{2:00} " -f $Date.Year, $Date.Month, $Date.Day
$now += "{0:00}:{1:00}:{2:00}" -f $Date.Hour, $Date.Minute, $Date.Second
$nsSuffix = ""
if ($ns) {
if ("$($Date.TimeOfDay)" -match "\.\d\d\d\d\d\d") {
$now += $matches[0]
$ms = $false
} else {
$ms = $true
$nsSuffix = "000"
}
}
if ($ms) {
$now += ".{0:000}$nsSuffix" -f $Date.MilliSecond
}
return $now
}
Function Log () {
Param(
[Parameter(Mandatory=$false, ValueFromPipeline=$true, Position=0)]
[String]$string
)
if (!(Test-Path $logDir)) {
New-Item -ItemType directory -Path $logDir | Out-Null
}
if ($String.length) {
$string = "$(Now) $pid $userName $string"
}
$string | Out-File -Encoding ASCII -Append "$logFile"
}
$scriptCopyCname = $scriptCopy -replace "\", "\" # Double backslashes. (The first \ is a regexp with \ escaped; The second is a plain string.)
$source = @"
using System;
using System.ServiceProcess;
using System.Diagnostics;
using System.Runtime.InteropServices; // SET STATUS
using System.ComponentModel; // SET STATUS
public enum ServiceType : int { // SET STATUS [
SERVICE_WIN32_OWN_PROCESS = 0x00000010,
SERVICE_WIN32_SHARE_PROCESS = 0x00000020,
}; // SET STATUS ]
public enum ServiceState : int { // SET STATUS [
SERVICE_STOPPED = 0x00000001,
SERVICE_START_PENDING = 0x00000002,
SERVICE_STOP_PENDING = 0x00000003,
SERVICE_RUNNING = 0x00000004,
SERVICE_CONTINUE_PENDING = 0x00000005,
SERVICE_PAUSE_PENDING = 0x00000006,
SERVICE_PAUSED = 0x00000007,
}; // SET STATUS ]
[StructLayout(LayoutKind.Sequential)] // SET STATUS [
public struct ServiceStatus {
public ServiceType dwServiceType;
public ServiceState dwCurrentState;
public int dwControlsAccepted;
public int dwWin32ExitCode;
public int dwServiceSpecificExitCode;
public int dwCheckPoint;
public int dwWaitHint;
}; // SET STATUS ]
public enum Win32Error : int { // WIN32 errors that we may need to use
NO_ERROR = 0,
ERROR_APP_INIT_FAILURE = 575,
ERROR_FATAL_APP_EXIT = 713,
ERROR_SERVICE_NOT_ACTIVE = 1062,
ERROR_EXCEPTION_IN_SERVICE = 1064,
ERROR_SERVICE_SPECIFIC_ERROR = 1066,
ERROR_PROCESS_ABORTED = 1067,
};
public class Service_$serviceName : ServiceBase { // $serviceName may begin with a digit; The class name must begin with a letter
private System.Diagnostics.EventLog eventLog; // EVENT LOG
private ServiceStatus serviceStatus; // SET STATUS
public Service_$serviceName() {
ServiceName = "$serviceName";
CanStop = true;
CanPauseAndContinue = false;
AutoLog = true;
eventLog = new System.Diagnostics.EventLog(); // EVENT LOG [
if (!System.Diagnostics.EventLog.SourceExists(ServiceName)) {
System.Diagnostics.EventLog.CreateEventSource(ServiceName, "$logName");
}
eventLog.Source = ServiceName;
eventLog.Log = "$logName"; // EVENT LOG ]
EventLog.WriteEntry(ServiceName, "$exeName $serviceName()"); // EVENT LOG
}
[DllImport("advapi32.dll", SetLastError=true)] // SET STATUS
private static extern bool SetServiceStatus(IntPtr handle, ref ServiceStatus serviceStatus);
protected override void OnStart(string [] args) {
EventLog.WriteEntry(ServiceName, "$exeName OnStart() // Entry. Starting script '$scriptCopyCname' -Start"); // EVENT LOG
// Set the service state to Start Pending. // SET STATUS [
// Only useful if the startup time is long. Not really necessary here for a 2s startup time.
serviceStatus.dwServiceType = ServiceType.SERVICE_WIN32_OWN_PROCESS;
serviceStatus.dwCurrentState = ServiceState.SERVICE_START_PENDING;
serviceStatus.dwWin32ExitCode = 0;
serviceStatus.dwWaitHint = 2000; // It takes about 2 seconds to start PowerShell
SetServiceStatus(ServiceHandle, ref serviceStatus); // SET STATUS ]
// Start a child process with another copy of this script
try {
Process p = new Process();
// Redirect the output stream of the child process.
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.FileName = "PowerShell.exe";
p.StartInfo.Arguments = "-ExecutionPolicy Bypass -c & '$scriptCopyCname' -Start"; // Works if path has spaces, but not if it contains ' quotes.
p.Start();
// Read the output stream first and then wait. (To avoid deadlocks says Microsoft!)
string output = p.StandardOutput.ReadToEnd();
// Wait for the completion of the script startup code, that launches the -Service instance
p.WaitForExit();
if (p.ExitCode != 0) throw new Win32Exception((int)(Win32Error.ERROR_APP_INIT_FAILURE));
// Success. Set the service state to Running. // SET STATUS
serviceStatus.dwCurrentState = ServiceState.SERVICE_RUNNING; // SET STATUS
} catch (Exception e) {
EventLog.WriteEntry(ServiceName, "$exeName OnStart() // Failed to start $scriptCopyCname. " + e.Message, EventLogEntryType.Error); // EVENT LOG
// Change the service state back to Stopped. // SET STATUS [
serviceStatus.dwCurrentState = ServiceState.SERVICE_STOPPED;
Win32Exception w32ex = e as Win32Exception; // Try getting the WIN32 error code
if (w32ex == null) { // Not a Win32 exception, but maybe the inner one is...
w32ex = e.InnerException as Win32Exception;
}
if (w32ex != null) { // Report the actual WIN32 error
serviceStatus.dwWin32ExitCode = w32ex.NativeErrorCode;
} else { // Make up a reasonable reason
serviceStatus.dwWin32ExitCode = (int)(Win32Error.ERROR_APP_INIT_FAILURE);
} // SET STATUS ]
} finally {
serviceStatus.dwWaitHint = 0; // SET STATUS
SetServiceStatus(ServiceHandle, ref serviceStatus); // SET STATUS
EventLog.WriteEntry(ServiceName, "$exeName OnStart() // Exit"); // EVENT LOG
}
}
protected override void OnStop() {
EventLog.WriteEntry(ServiceName, "$exeName OnStop() // Entry"); // EVENT LOG
// Start a child process with another copy of ourselves
Process p = new Process();
// Redirect the output stream of the child process.
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.FileName = "PowerShell.exe";
p.StartInfo.Arguments = "-c & '$scriptCopyCname' -Stop"; // Works if path has spaces, but not if it contains ' quotes.
p.Start();
// Read the output stream first and then wait.
string output = p.StandardOutput.ReadToEnd();
// Wait for the PowerShell script to be fully stopped.
p.WaitForExit();
// Change the service state back to Stopped. // SET STATUS
serviceStatus.dwCurrentState = ServiceState.SERVICE_STOPPED; // SET STATUS
SetServiceStatus(ServiceHandle, ref serviceStatus); // SET STATUS
EventLog.WriteEntry(ServiceName, "$exeName OnStop() // Exit"); // EVENT LOG
}
public static void Main() {
System.ServiceProcess.ServiceBase.Run(new Service_$serviceName());
}
}
"@
# Check if we're running as a real user, or as the SYSTEM = As a service
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$userName = $identity.Name # Ex: "NT AUTHORITY\SYSTEM" or "Domain\Administrator"
$authority,$name = $username -split "\"
$isSystem = $identity.IsSystem # Do not test ($userName -eq "NT AUTHORITY\SYSTEM"), as this fails in non-English systems.
# Log "# `$userName = `"$userName`" ; `$isSystem = $isSystem"
if ($Setup) {Log ""} # Insert one blank line to separate test sessions logs
Log $MyInvocation.Line # The exact command line that was used to start us
# The following commands write to the event log, but we need to make sure the PSService source is defined.
New-EventLog -LogName $logName -Source $serviceName -ea SilentlyContinue
# Workaround for PowerShell v2 bug: $PSCmdlet Not yet defined in Param() block
$Status = ($PSCmdlet.ParameterSetName -eq 'Status')
if ($Start) { # Start the service
if ($isSystem) { # If running as SYSTEM, ie. invoked as a service
# Do whatever is necessary to start the service script instance
Log "$scriptName -Start: Starting script '$scriptFullName' -Service"
Write-EventLog -LogName $logName -Source $serviceName -EventId 1001 -EntryType Information -Message "$scriptName -Start: Starting script '$scriptFullName' -Service"
Start-Process PowerShell.exe -ArgumentList ("-c & '$scriptFullName' -Service")
} else {
Write-Verbose "Starting service $serviceName"
Write-EventLog -LogName $logName -Source $serviceName -EventId 1002 -EntryType Information -Message "$scriptName -Start: Starting service $serviceName"
Start-Service $serviceName # Ask Service Control Manager to start it
}
return
}
if ($Stop) { # Stop the service
if ($isSystem) { # If running as SYSTEM, ie. invoked as a service
# Do whatever is necessary to stop the service script instance
Write-EventLog -LogName $logName -Source $serviceName -EventId 1003 -EntryType Information -Message "$scriptName -Stop: Stopping script $scriptName -Service"
Log "$scriptName -Stop: Stopping script $scriptName -Service"
#region Solr stop
&$sorlStartCmd stop -p $solrPort
#endregion
} else {
Write-Verbose "Stopping service $serviceName"
Write-EventLog -LogName $logName -Source $serviceName -EventId 1004 -EntryType Information -Message "$scriptName -Stop: Stopping service $serviceName"
Stop-Service $serviceName # Ask Service Control Manager to stop it
}
return
}
if ($Restart) { # Restart the service
& $scriptFullName -Stop
& $scriptFullName -Start
return
}
if ($Status) { # Get the current service status
$spid = $null
$processes = @(Get-WmiObject Win32_Process -filter "Name = 'powershell.exe'" | Where-Object {
$_.CommandLine -match ".*$scriptCopyCname.*-Service"
})
foreach ($process in $processes) { # There should be just one, but be prepared for surprises.
$spid = $process.ProcessId
Write-Verbose "$serviceName Process ID = $spid"
}
# if (Test-Path "HKLM:\SYSTEM\CurrentControlSet\services$serviceName") {}
try {
$pss = Get-Service $serviceName -ea stop # Will error-out if not installed
} catch {
"Not Installed"
return
}
$pss.Status
if (($pss.Status -eq "Running") -and (!$spid)) { # This happened during the debugging phase
Write-Error "The Service Control Manager thinks $serviceName is started, but $serviceName.ps1 -Service is not running."
exit 1
}
return
}
if ($Setup) { # Install the service
# Check if it's necessary
try {
$pss = Get-Service $serviceName -ea stop # Will error-out if not installed
# Check if this script is newer than the installed copy.
if ((Get-Item $scriptCopy -ea SilentlyContinue).LastWriteTime -lt (Get-Item $scriptFullName -ea SilentlyContinue).LastWriteTime) {
Write-Verbose "Service $serviceName is already Installed, but requires upgrade"
& $scriptFullName -Remove
throw "continue"
} else {
Write-Verbose "Service $serviceName is already Installed, and up-to-date"
}
exit 0
} catch {
# This is the normal case here. Do not throw or write any error!
Write-Debug "Installation is necessary" # Also avoids a ScriptAnalyzer warning
# And continue with the installation.
}
if (!(Test-Path $installDir)) {
New-Item -ItemType directory -Path $installDir | Out-Null
}
# Copy the service script into the installation directory
if ($ScriptFullName -ne $scriptCopy) {
Write-Verbose "Installing $scriptCopy"
Copy-Item $ScriptFullName $scriptCopy
}
# Generate the service .EXE from the C# source embedded in this script
try {
Write-Verbose "Compiling $exeFullName"
Add-Type -TypeDefinition $source -Language CSharp -OutputAssembly $exeFullName -OutputType ConsoleApplication -ReferencedAssemblies "System.ServiceProcess" -Debug:$false
} catch {
$msg = $_.Exception.Message
Write-error "Failed to create the $exeFullName service stub. $msg"
exit 1
}
# Register the service
Write-Verbose "Registering service $serviceName"
$pss = New-Service $serviceName $exeFullName -DisplayName $serviceDisplayName -Description $ServiceDescription -StartupType Automatic
return
}
if ($Remove) { # Uninstall the service
# Check if it's necessary
try {
$pss = Get-Service $serviceName -ea stop # Will error-out if not installed
} catch {
Write-Verbose "Already uninstalled"
return
}
Stop-Service $serviceName # Make sure it's stopped
# In the absence of a Remove-Service applet, use sc.exe instead.
Write-Verbose "Removing service $serviceName"
$msg = sc.exe delete $serviceName
if ($LastExitCode) {
Write-Error "Failed to remove the service ${serviceName}: $msg"
exit 1
} else {
Write-Verbose $msg
}
# Remove the installed files
if (Test-Path $installDir) {
foreach ($ext in ("exe", "pdb", "ps1")) {
$file = "$installDir$serviceName.$ext"
if (Test-Path $file) {
Write-Verbose "Deleting file $file"
Remove-Item $file
}
}
if (!(@(Get-ChildItem $installDir -ea SilentlyContinue)).Count) {
Write-Verbose "Removing directory $installDir"
Remove-Item $installDir
}
}
return
}
if ($Service) { # Run the service
Write-EventLog -LogName $logName -Source $serviceName -EventId 1005 -EntryType Information -Message "$scriptName -Service # Beginning background job"
# Do the service background job
try
{
#region Solr start
Log "$scriptName Starting $sorlStartCmd with parameteres start -f -p $solrPort -m $solrMemory"
&$sorlStartCmd start -f -p $solrPort -m $solrMemory
#endregion
}
catch
{ # An exception occurred while runnning the service
$msg = $_.Exception.Message
$line = $_.InvocationInfo.ScriptLineNumber
Log "$scriptName -Service # Error at line ${line}: $msg"
}
finally
{
# Invoked in all cases: Exception or normally by -Stop
# Flush all leftover events (There may be some that arrived after we exited the while event loop, but before we unregistered the events)
$events = Get-Event | Remove-Event
# Log a termination event, no matter what the cause is.
Write-EventLog -LogName $logName -Source $serviceName -EventId 1006 -EntryType Information -Message "$script -Service # Exiting"
Log "$scriptName -Service # Exiting"
}
return
}