diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..7222e003 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,30 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: 'Type: Bug' +assignees: '' + +--- + +**Description** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Details:** +- Windows Version: [ e.g. Windows 10 ] +- Context release version: [ e.g. 6.6.0 ] +- OpenNebula version: [ e.g. 6.6.1 ] + +**Additional context** +Add any other context about the problem here. + +## Progress Status +- [ ] Code committed +- [ ] Testing - QA +- [ ] Documentation (Release notes - resolved issues, compatibility, known issues) diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..f94532d8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,22 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: 'Type: Feature' +assignees: '' + +--- + +**Description** +Brief description of the new functionality + +**Use case** +How are you going to use this new feature? Why do you need it? + +**Additional Context** +Please feel free to add any other context or screenshots about the feature request here. Or any other alternative you have considered to address this new feature. + +## Progress Status +- [ ] Code committed +- [ ] Testing - QA +- [ ] Documentation (Release notes - resolved issues, compatibility, known issues) diff --git a/.github/ISSUE_TEMPLATE/support-windows-version.md b/.github/ISSUE_TEMPLATE/support-windows-version.md new file mode 100644 index 00000000..546e0306 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/support-windows-version.md @@ -0,0 +1,14 @@ +--- +name: Support Windows version +about: Support a Windows version officially supported by Microsoft +title: Support Windows +labels: 'Type: Feature' +assignees: '' + +--- + +Progress +- [ ] Image Built and operational +- [ ] Testing - QA +- [ ] [Platform notes](https://docs.opennebula.io/6.6/intro_release_notes/release_notes/platform_notes.html#windows-contextualization-packages) +- [ ] [Windows KB](https://support.opennebula.pro/hc/en-us/articles/360042898271-How-to-Build-an-Image-for-Windows-Virtual-Machines) diff --git a/.github/workflows/powershell.yml b/.github/workflows/powershell.yml new file mode 100644 index 00000000..d241bde4 --- /dev/null +++ b/.github/workflows/powershell.yml @@ -0,0 +1,27 @@ +name: PowerShell Linting + +on: [push, pull_request] + +jobs: + # https://docs.github.com/en/actions/guides/building-and-testing-powershell#using-psscriptanalyzer-to-lint-code + lint-with-PSScriptAnalyzer: + name: Install and run PSScriptAnalyzer + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install PSScriptAnalyzer module + shell: pwsh + run: | + Set-PSRepository PSGallery -InstallationPolicy Trusted + Install-Module PSScriptAnalyzer -ErrorAction Stop + - name: Lint with PSScriptAnalyzer + shell: pwsh + run: | + Invoke-ScriptAnalyzer -Path src/*.ps1 -Recurse -Outvariable issues + $errors = $issues.Where({$_.Severity -eq 'Error'}) + $warnings = $issues.Where({$_.Severity -eq 'Warning'}) + if ($errors) { + Write-Error "There were $($errors.Count) errors and $($warnings.Count) warnings total." -ErrorAction Stop + } else { + Write-Output "There were $($errors.Count) errors and $($warnings.Count) warnings total." + } diff --git a/.gitignore b/.gitignore index 9faf0b3a..01261259 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ build context context-linux/out/* +context-windows/out/* +context-windows/*.msi +context-windows/rhsrvany.exe +context-windows/*.un~ +context-windows/.idea diff --git a/context-windows/README.md b/context-windows/README.md new file mode 100644 index 00000000..2e040d9b --- /dev/null +++ b/context-windows/README.md @@ -0,0 +1,102 @@ +# OpenNebula Windows VM Contextualization + +## Description + +This addon provides contextualization package for the Windows +guest virtual machines running in the OpenNebula cloud. Based +on the provided contextualization parameters, the packages prepare the +networking in the running guest virt. machine, set +passwords, run custom start scripts, and many others. + +## Download + +Latest versions can be downloaded from the +[release page](https://github.com/OpenNebula/addon-context-windows/releases). +Check the supported OpenNebula versions for each release. + +## Install + +Documentation on packages installation and guest contextualization can +be found in the latest stable +[OpenNebula Operation Guide](http://docs.opennebula.org/stable/operation/vm_setup/context_overview.html). +For beta releases, refer to the latest +[development documentation](http://docs.opennebula.org/devel/operation/vm_setup/context_overview.html). + +## Build own package + +### Requirements + +* **Linux host** +* latest [msitools](https://wiki.gnome.org/msitools) +* binary [nssm.exe](https://nssm.cc/) [present] +* binary [rhsrvany.exe](https://github.com/rwmjones/rhsrvany) [optional] +* `mkisofs` + +The service manager **NSSM** is the preferred tool to manage services because +it handles long running services better and more correctly (srvany/rhsrvany +fails to terminate its child processes on stop). NSSM is in public domain and +the binary is part of this repo. There are both 32bit and 64bit versions - +currently 32bit version is used because it covers broader set of systems. + +If you wish to use rhsrvany instead then you must set the shell variable +`SRV_MANAGER` to `rhsrvany` otherwise it will default to `nssm`. + +On RHEL (CentOS) and Fedora systems, the required binary +[rhsrvany.exe](https://github.com/rwmjones/rhsrvany) is distributed as part +of the package `virt-v2v` and placed into `/usr/share/virt-tools/rhsrvany.exe`. +Please copy the EXE into your local repository clone before creating the MSI. + +### Steps + +Script `generate.sh` builds the MSI package. It's a wrapper around +the `wixl` command from `msitools`. It reads the `package.wxs`, a package +definition in the WiX-like XML format. Package name or version can be +overridden by env. variables `NAME` and `VERSION`. For example: + +``` +$ TARGET=msi ./generate.sh +$ NAME=one-context TARGET=msi ./generate.sh +$ VERSION=1.0.0 TARGET=msi ./generate.sh +``` + +New package is created as `${NAME}-${VERSION}.msi`, +e.g. `one-context-1.0.0.msi` in the `out/` directory. + +You can also built both the iso and msi targets like this: + +``` +$ ./generate-all.sh +``` + +Or with a different service manager and explicit version: + +``` +$ env SRV_MANAGER=rhsrvany VERSION=5.13 ./generate-all.sh +``` + +Please ignore following assertion on package build, which is caused +by skipping the attribute `Start` in tag `ServiceControl`. The parameter +is optional in WiX specification, but the `msitools` still counts with it. +Despite that, the package is built. + +``` +(wixl:22764): wixl-CRITICAL **: wixl_wix_builder_install_mode_to_event: assertion 'modeString != NULL' failed +``` + +## Acknowledgements + +This addon is largely based upon the work by André Monteiro and Tiago Batista in the [DETI/IEETA Universidade de Aveiro](http://www.ua.pt/). The original guide is available here: [OpenNebula - IEETA](http://wiki.ieeta.pt/wiki/index.php/OpenNebula) + +## License + +Copyright 2002-2021, OpenNebula Project, OpenNebula Systems (formerly C12G Labs) + +Licensed under the Apache License, Version 2.0 (the "License"); you may +not use this file except in compliance with the License. You may obtain +a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/context-windows/generate-all.sh b/context-windows/generate-all.sh new file mode 100755 index 00000000..e2c04127 --- /dev/null +++ b/context-windows/generate-all.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +set -e + +export DATE=$(date +%Y%m%d) +TARGETS='msi iso' + +for TARGET in $TARGETS; do + TARGET="${TARGET}" ./generate.sh +done + +echo +echo "The packages are here:" +echo "--------------------------------------------------------------------------------" +find out -type f diff --git a/context-windows/generate.sh b/context-windows/generate.sh new file mode 100755 index 00000000..31fedcfe --- /dev/null +++ b/context-windows/generate.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash + +# -------------------------------------------------------------------------- # +# Copyright 2002-2021, OpenNebula Project, OpenNebula Systems # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); you may # +# not use this file except in compliance with the License. You may obtain # +# a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +#--------------------------------------------------------------------------- # + +if [ -z "${TARGET}" ]; then + echo 'Error: env. variable TARGET not set' >&2 + exit 1 +fi + +### + +if [ -z "${RELEASE}" ]; then + if git describe --contains $(git rev-parse HEAD) &>/dev/null; then + RELEASE=1 + else + DATE=${DATE:-$(date +%Y%m%d)} + GIT=$(git rev-parse --short HEAD) + RELEASE="${DATE}git${GIT}" + fi +fi + +### + +NAME=${NAME:-one-context} +VERSION=${VERSION:-6.6.1} +RELEASE=${RELEASE:-1} +LABEL="${NAME}-${VERSION}" +SRV_MANAGER="${SRV_MANAGER:-nssm}" + +if [ "${RELEASE}" = '1' ]; then + FILENAME=${FILENAME:-${NAME}-${VERSION}.${TARGET}} +else + FILENAME=${FILENAME:-${NAME}-${VERSION}-${RELEASE}.${TARGET}} +fi + +# cleanup +if [ -z "${OUT}" ]; then + OUT="out/${FILENAME}" + mkdir -p $(dirname "${OUT}") + rm -rf "${OUT}" +fi + +set -e + +if [ "${TARGET}" = 'msi' ]; then + case "${SRV_MANAGER}" in + nssm) + _SRV_BINARY_NAME='nssm.exe' + _SRV_BINARY_FILE='nssm/win32/nssm.exe' + _SRV_BINARY_ARGS='' + ;; + rhsrvany) + _SRV_BINARY_NAME='rhsrvany.exe' + _SRV_BINARY_FILE='rhsrvany.exe' + _SRV_BINARY_ARGS='-s onecontext' + + # in the rhsrvany case we might be able to use some os package + if [ ! -f rhsrvany.exe ]; then + if [ -f /usr/share/virt-tools/rhsrvany.exe ]; then + cp /usr/share/virt-tools/rhsrvany.exe . + fi + fi + ;; + esac + + if [ ! -f "${_SRV_BINARY_FILE}" ] ; then + echo "Error: The service binary is missing: ${_SRV_BINARY_FILE}" + exit 1 + else + echo "Info: Using the binary: ${_SRV_BINARY_FILE}" + fi >&2 + + wixl -D Version="${VERSION}" \ + -D SrvBinaryName="${_SRV_BINARY_NAME}" \ + -D SrvBinaryFile="${_SRV_BINARY_FILE}" \ + -D SrvBinaryArgs="${_SRV_BINARY_ARGS}" \ + -o "${OUT}" package.wxs + +elif [ "${TARGET}" = 'iso' ]; then + mkisofs -J -R -input-charset utf8 \ + -m '*.iso' \ + -V "${LABEL}" \ + -o "${OUT}" \ + $(dirname "${OUT}") + +else + echo "Error: Invalid target '${TARGET}'" >&2 + exit 1 +fi + +echo $(basename ${OUT}) diff --git a/context-windows/nssm b/context-windows/nssm new file mode 120000 index 00000000..72217236 --- /dev/null +++ b/context-windows/nssm @@ -0,0 +1 @@ +nssm-2.24 \ No newline at end of file diff --git a/context-windows/nssm-2.24/README.txt b/context-windows/nssm-2.24/README.txt new file mode 100644 index 00000000..7fcb85b6 --- /dev/null +++ b/context-windows/nssm-2.24/README.txt @@ -0,0 +1,692 @@ +NSSM: The Non-Sucking Service Manager +Version 2.24, 2014-08-31 + +NSSM is a service helper program similar to srvany and cygrunsrv. It can +start any application as an NT service and will restart the service if it +fails for any reason. + +NSSM also has a graphical service installer and remover. + +Full documentation can be found online at + + http://nssm.cc/ + +Since version 2.0, the GUI can be bypassed by entering all appropriate +options on the command line. + +Since version 2.1, NSSM can be compiled for x64 platforms. +Thanks Benjamin Mayrargue. + +Since version 2.2, NSSM can be configured to take different actions +based on the exit code of the managed application. + +Since version 2.3, NSSM logs to the Windows event log more elegantly. + +Since version 2.5, NSSM respects environment variables in its parameters. + +Since version 2.8, NSSM tries harder to shut down the managed application +gracefully and throttles restart attempts if the application doesn't run +for a minimum amount of time. + +Since version 2.11, NSSM respects srvany's AppEnvironment parameter. + +Since version 2.13, NSSM is translated into French. +Thanks François-Régis Tardy. + +Since version 2.15, NSSM is translated into Italian. +Thanks Riccardo Gusmeroli. + +Since version 2.17, NSSM can try to shut down console applications by +simulating a Control-C keypress. If they have installed a handler routine +they can clean up and shut down gracefully on receipt of the event. + +Since version 2.17, NSSM can redirect the managed application's I/O streams +to an arbitrary path. + +Since version 2.18, NSSM can be configured to wait a user-specified amount +of time for the application to exit when shutting down. + +Since version 2.19, many more service options can be configured with the +GUI installer as well as via the registry. + +Since version 2.19, NSSM can add to the service's environment by setting +AppEnvironmentExtra in place of or in addition to the srvany-compatible +AppEnvironment. + +Since version 2.22, NSSM can set the managed application's process priority +and CPU affinity. + +Since version 2.22, NSSM can apply an unconditional delay before restarting +an application which has exited. + +Since version 2.22, NSSM can rotate existing output files when redirecting I/O. + +Since version 2.22, NSSM can set service display name, description, startup +type, log on details and dependencies. + +Since version 2.22, NSSM can manage existing services. + + +Usage +----- +In the usage notes below, arguments to the program may be written in angle +brackets and/or square brackets. means you must insert the +appropriate string and [] means the string is optional. See the +examples below... + +Note that everywhere appears you may substitute the +service's display name. + + +Installation using the GUI +-------------------------- +To install a service, run + + nssm install + +You will be prompted to enter the full path to the application you wish +to run and any command line options to pass to that application. + +Use the system service manager (services.msc) to control advanced service +properties such as startup method and desktop interaction. NSSM may +support these options at a later time... + + +Installation using the command line +----------------------------------- +To install a service, run + + nssm install [] + +NSSM will then attempt to install a service which runs the named application +with the given options (if you specified any). + +Don't forget to enclose paths in "quotes" if they contain spaces! + +If you want to include quotes in the options you will need to """quote""" the +quotes. + + +Managing the service +-------------------- +NSSM will launch the application listed in the registry when you send it a +start signal and will terminate it when you send a stop signal. So far, so +much like srvany. But NSSM is the Non-Sucking service manager and can take +action if/when the application dies. + +With no configuration from you, NSSM will try to restart itself if it notices +that the application died but you didn't send it a stop signal. NSSM will +keep trying, pausing between each attempt, until the service is successfully +started or you send it a stop signal. + +NSSM will pause an increasingly longer time between subsequent restart attempts +if the service fails to start in a timely manner, up to a maximum of four +minutes. This is so it does not consume an excessive amount of CPU time trying +to start a failed application over and over again. If you identify the cause +of the failure and don't want to wait you can use the Windows service console +(where the service will be shown in Paused state) to send a continue signal to +NSSM and it will retry within a few seconds. + +By default, NSSM defines "a timely manner" to be within 1500 milliseconds. +You can change the threshold for the service by setting the number of +milliseconds as a REG_DWORD value in the registry at +HKLM\SYSTEM\CurrentControlSet\Services\\Parameters\AppThrottle. + +Alternatively, NSSM can pause for a configurable amount of time before +attempting to restart the application even if it successfully ran for the +amount of time specified by AppThrottle. NSSM will consult the REG_DWORD value +at HKLM\SYSTEM\CurrentControlSet\Services\\Parameters\AppRestartDelay +for the number of milliseconds to wait before attempting a restart. If +AppRestartDelay is set and the application is determined to be subject to +throttling, NSSM will pause the service for whichever is longer of the +configured restart delay and the calculated throttle period. + +If AppRestartDelay is missing or invalid, only throttling will be applied. + +NSSM will look in the registry under +HKLM\SYSTEM\CurrentControlSet\Services\\Parameters\AppExit for +string (REG_EXPAND_SZ) values corresponding to the exit code of the application. +If the application exited with code 1, for instance, NSSM will look for a +string value under AppExit called "1" or, if it does not find it, will +fall back to the AppExit (Default) value. You can find out the exit code +for the application by consulting the system event log. NSSM will log the +exit code when the application exits. + +Based on the data found in the registry, NSSM will take one of three actions: + +If the value data is "Restart" NSSM will try to restart the application as +described above. This is its default behaviour. + +If the value data is "Ignore" NSSM will not try to restart the application +but will continue running itself. This emulates the (usually undesirable) +behaviour of srvany. The Windows Services console would show the service +as still running even though the application has exited. + +If the value data is "Exit" NSSM will exit gracefully. The Windows Services +console would show the service as stopped. If you wish to provide +finer-grained control over service recovery you should use this code and +edit the failure action manually. Please note that Windows versions prior +to Vista will not consider such an exit to be a failure. On older versions +of Windows you should use "Suicide" instead. + +If the value data is "Suicide" NSSM will simulate a crash and exit without +informing the service manager. This option should only be used for +pre-Vista systems where you wish to apply a service recovery action. Note +that if the monitored application exits with code 0, NSSM will only honour a +request to suicide if you explicitly configure a registry key for exit code 0. +If only the default action is set to Suicide NSSM will instead exit gracefully. + + +Application priority +-------------------- +NSSM can set the priority class of the managed application. NSSM will look in +the registry under HKLM\SYSTEM\CurrentControlSet\Services\\Parameters +for the REG_DWORD entry AppPriority. Valid values correspond to arguments to +SetPriorityClass(). If AppPriority() is missing or invalid the +application will be launched with normal priority. + + +Processor affinity +------------------ +NSSM can set the CPU affinity of the managed application. NSSM will look in +the registry under HKLM\SYSTEM\CurrentControlSet\Services\\Parameters +for the REG_SZ entry AppAffinity. It should specify a comma-separated listed +of zero-indexed processor IDs. A range of processors may optionally be +specified with a dash. No other characters are allowed in the string. + +For example, to specify the first; second; third and fifth CPUs, an appropriate +AppAffinity would be 0-2,4. + +If AppAffinity is missing or invalid, NSSM will not attempt to restrict the +application to specific CPUs. + +Note that the 64-bit version of NSSM can configure a maximum of 64 CPUs in this +way and that the 32-bit version can configure a maxium of 32 CPUs even when +running on 64-bit Windows. + + +Stopping the service +-------------------- +When stopping a service NSSM will attempt several different methods of killing +the monitored application, each of which can be disabled if necessary. + +First NSSM will attempt to generate a Control-C event and send it to the +application's console. Batch scripts or console applications may intercept +the event and shut themselves down gracefully. GUI applications do not have +consoles and will not respond to this method. + +Secondly NSSM will enumerate all windows created by the application and send +them a WM_CLOSE message, requesting a graceful exit. + +Thirdly NSSM will enumerate all threads created by the application and send +them a WM_QUIT message, requesting a graceful exit. Not all applications' +threads have message queues; those which do not will not respond to this +method. + +Finally NSSM will call TerminateProcess() to request that the operating +system forcibly terminate the application. TerminateProcess() cannot be +trapped or ignored, so in most circumstances the application will be killed. +However, there is no guarantee that it will have a chance to perform any +tidyup operations before it exits. + +Any or all of the methods above may be disabled. NSSM will look for the +HKLM\SYSTEM\CurrentControlSet\Services\\Parameters\AppStopMethodSkip +registry value which should be of type REG_DWORD set to a bit field describing +which methods should not be applied. + + If AppStopMethodSkip includes 1, Control-C events will not be generated. + If AppStopMethodSkip includes 2, WM_CLOSE messages will not be posted. + If AppStopMethodSkip includes 4, WM_QUIT messages will not be posted. + If AppStopMethodSkip includes 8, TerminateProcess() will not be called. + +If, for example, you knew that an application did not respond to Control-C +events and did not have a thread message queue, you could set AppStopMethodSkip +to 5 and NSSM would not attempt to use those methods to stop the application. + +Take great care when including 8 in the value of AppStopMethodSkip. If NSSM +does not call TerminateProcess() it is possible that the application will not +exit when the service stops. + +By default NSSM will allow processes 1500ms to respond to each of the methods +described above before proceeding to the next one. The timeout can be +configured on a per-method basis by creating REG_DWORD entries in the +registry under HKLM\SYSTEM\CurrentControlSet\Services\\Parameters. + + AppStopMethodConsole + AppStopMethodWindow + AppStopMethodThreads + +Each value should be set to the number of milliseconds to wait. Please note +that the timeout applies to each process in the application's process tree, +so the actual time to shutdown may be longer than the sum of all configured +timeouts if the application spawns multiple subprocesses. + + +Console window +-------------- +By default, NSSM will create a console window so that applications which +are capable of reading user input can do so - subject to the service being +allowed to interact with the desktop. + +Creation of the console can be suppressed by setting the integer (REG_DWORD) +HKLM\SYSTEM\CurrentControlSet\Services\\Parameters\AppNoConsole +registry value to 1. + + +I/O redirection +--------------- +NSSM can redirect the managed application's I/O to any path capable of being +opened by CreateFile(). This enables, for example, capturing the log output +of an application which would otherwise only write to the console or accepting +input from a serial port. + +NSSM will look in the registry under +HKLM\SYSTEM\CurrentControlSet\Services\\Parameters for the keys +corresponding to arguments to CreateFile(). All are optional. If no path is +given for a particular stream it will not be redirected. If a path is given +but any of the other values are omitted they will be receive sensible defaults. + + AppStdin: Path to receive input. + AppStdout: Path to receive output. + AppStderr: Path to receive error output. + +Parameters for CreateFile() are providing with the "AppStdinShareMode", +"AppStdinCreationDisposition" and "AppStdinFlagsAndAttributes" values (and +analogously for stdout and stderr). + +In general, if you want the service to log its output, set AppStdout and +AppStderr to the same path, eg C:\Users\Public\service.log, and it should +work. Remember, however, that the path must be accessible to the user +running the service. + + +File rotation +------------- +When using I/O redirection, NSSM can rotate existing output files prior to +opening stdout and/or stderr. An existing file will be renamed with a +suffix based on the file's last write time, to millisecond precision. For +example, the file nssm.log might be rotated to nssm-20131221T113939.457.log. + +NSSM will look in the registry under +HKLM\SYSTEM\CurrentControlSet\Services\\Parameters for REG_DWORD +entries which control how rotation happens. + +If AppRotateFiles is missing or set to 0, rotation is disabled. Any non-zero +value enables rotation. + +If AppRotateSeconds is non-zero, a file will not be rotated if its last write +time is less than the given number of seconds in the past. + +If AppRotateBytes is non-zero, a file will not be rotated if it is smaller +than the given number of bytes. 64-bit file sizes can be handled by setting +a non-zero value of AppRotateBytesHigh. + +Rotation is independent of the CreateFile() parameters used to open the files. +They will be rotated regardless of whether NSSM would otherwise have appended +or replaced them. + +NSSM can also rotate files which hit the configured size threshold while the +service is running. Additionally, you can trigger an on-demand rotation by +running the command + + nssm rotate + +On-demand rotations will happen after the next line of data is read from +the managed application, regardless of the value of AppRotateBytes. Be aware +that if the application is not particularly verbose the rotation may not +happen for some time. + +To enable online and on-demand rotation, set AppRotateOnline to a non-zero +value. + +Note that online rotation requires NSSM to intercept the application's I/O +and create the output files on its behalf. This is more complex and +error-prone than simply redirecting the I/O streams before launching the +application. Therefore online rotation is not enabled by default. + + +Environment variables +--------------------- +NSSM can replace or append to the managed application's environment. Two +multi-valued string (REG_MULTI_SZ) registry values are recognised under +HKLM\SYSTEM\CurrentControlSet\Services\\Parameters. + +AppEnvironment defines a list of environment variables which will override +the service's environment. AppEnvironmentExtra defines a list of +environment variables which will be added to the service's environment. + +Each entry in the list should be of the form KEY=VALUE. It is possible to +omit the VALUE but the = symbol is mandatory. + +Environment variables listed in both AppEnvironment and AppEnvironmentExtra +are subject to normal expansion, so it is possible, for example, to update the +system path by setting "PATH=C:\bin;%PATH%" in AppEnvironmentExtra. Variables +are expanded in the order in which they appear, so if you want to include the +value of one variable in another variable you should declare the dependency +first. + +Because variables defined in AppEnvironment override the existing +environment it is not possible to refer to any variables which were previously +defined. + +For example, the following AppEnvironment block: + + PATH=C:\Windows\System32;C:\Windows + PATH=C:\bin;%PATH% + +Would result in a PATH of "C:\bin;C:\Windows\System32;C:\Windows" as expected. + +Whereas the following AppEnvironment block: + + PATH=C:\bin;%PATH% + +Would result in a path containing only C:\bin and probably cause the +application to fail to start. + +Most people will want to use AppEnvironmentExtra exclusively. srvany only +supports AppEnvironment. + + +Managing services using the GUI +------------------------------- +NSSM can edit the settings of existing services with the same GUI that is +used to install them. Run + + nssm edit + +to bring up the GUI. + +NSSM offers limited editing capabilities for services other than those which +run NSSM itself. When NSSM is asked to edit a service which does not have +the App* registry settings described above, the GUI will allow editing only +system settings such as the service display name and description. + + +Managing services using the command line +---------------------------------------- +NSSM can retrieve or set individual service parameters from the command line. +In general the syntax is as follows, though see below for exceptions. + + nssm get + + nssm set + +Parameters can also be reset to their default values. + + nssm reset + +The parameter names recognised by NSSM are the same as the registry entry +names described above, eg AppDirectory. + +NSSM offers limited editing capabilities for Services other than those which +run NSSM itself. The parameters recognised are as follows: + + Description: Service description. + DisplayName: Service display name. + ImagePath: Path to the service executable. + ObjectName: User account which runs the service. + Name: Service key name. + Start: Service startup type. + Type: Service type. + +These correspond to the registry values under the service's key +HKLM\SYSTEM\CurrentControlSet\Services\. + + +Note that NSSM will concatenate all arguments passed on the command line +with spaces to form the value to set. Thus the following two invocations +would have the same effect. + + nssm set Description "NSSM managed service" + + nssm set Description NSSM managed service + + +Non-standard parameters +----------------------- +The AppEnvironment and AppEnvironmentExtra parameters recognise an +additional argument when querying the environment. The following syntax +will print all extra environment variables configured for a service + + nssm get AppEnvironmentExtra + +whereas the syntax below will print only the value of the CLASSPATH +variable if it is configured in the environment block, or the empty string +if it is not configured. + + nssm get AppEnvironmentExtra CLASSPATH + +When setting an environment block, each variable should be specified as a +KEY=VALUE pair in separate command line arguments. For example: + + nssm set AppEnvironment CLASSPATH=C:\Classes TEMP=C:\Temp + + +The AppExit parameter requires an additional argument specifying the exit +code to get or set. The default action can be specified with the string +Default. + +For example, to get the default exit action for a service you should run + + nssm get AppExit Default + +To get the exit action when the application exits with exit code 2, run + + nssm get AppExit 2 + +Note that if no explicit action is configured for a specified exit code, +NSSM will print the default exit action. + +To set configure the service to stop when the application exits with an +exit code of 2, run + + nssm set AppExit 2 Exit + + +The AppPriority parameter is used to set the priority class of the +managed application. Valid priorities are as follows: + + REALTIME_PRIORITY_CLASS + HIGH_PRIORITY_CLASS + ABOVE_NORMAL_PRIORITY_CLASS + NORMAL_PRIORITY_CLASS + BELOW_NORMAL_PRIORITY_CLASS + IDLE_PRIORITY_CLASS + + +The DependOnGroup and DependOnService parameters are used to query or set +the dependencies for the service. When setting dependencies, each service +or service group (preceded with the + symbol) should be specified in +separate command line arguments. For example: + + nssm set DependOnService RpcSs LanmanWorkstation + + +The Name parameter can only be queried, not set. It returns the service's +registry key name. This may be useful to know if you take advantage of +the fact that you can substitute the service's display name anywhere where +the syntax calls for . + + +The ObjectName parameter requires an additional argument only when setting +a username. The additional argument is the password of the user. + +To retrieve the username, run + + nssm get ObjectName + +To set the username and password, run + + nssm set ObjectName + +Note that the rules of argument concatenation still apply. The following +invocation is valid and will have the expected effect. + + nssm set ObjectName correct horse battery staple + +The following well-known usernames do not need a password. The password +parameter can be omitted when using them: + + "LocalSystem" aka "System" aka "NT Authority\System" + "LocalService" aka "Local Service" aka "NT Authority\Local Service" + "NetworkService" aka "Network Service" aka "NT Authority\Network Service" + + +The Start parameter is used to query or set the startup type of the service. +Valid service startup types are as follows: + + SERVICE_AUTO_START: Automatic startup at boot. + SERVICE_DELAYED_START: Delayed startup at boot. + SERVICE_DEMAND_START: Manual service startup. + SERVICE_DISABLED: The service is disabled. + +Note that SERVICE_DELAYED_START is not supported on versions of Windows prior +to Vista. NSSM will set the service to automatic startup if delayed start is +unavailable. + + +The Type parameter is used to query or set the service type. NSSM recognises +all currently documented service types but will only allow setting one of two +types: + + SERVICE_WIN32_OWN_PROCESS: A standalone service. This is the default. + SERVICE_INTERACTIVE_PROCESS: A service which can interact with the desktop. + +Note that a service may only be configured as interactive if it runs under +the LocalSystem account. The safe way to configure an interactive service +is in two stages as follows. + + nssm reset ObjectName + nssm set Type SERVICE_INTERACTIVE_PROCESS + + +Controlling services using the command line +------------------------------------------- +NSSM offers rudimentary service control features. + + nssm start + + nssm restart + + nssm stop + + nssm status + + +Removing services using the GUI +------------------------------- +NSSM can also remove services. Run + + nssm remove + +to remove a service. You will prompted for confirmation before the service +is removed. Try not to remove essential system services... + + +Removing service using the command line +--------------------------------------- +To remove a service without confirmation from the GUI, run + + nssm remove confirm + +Try not to remove essential system services... + + +Logging +------- +NSSM logs to the Windows event log. It registers itself as an event log source +and uses unique event IDs for each type of message it logs. New versions may +add event types but existing event IDs will never be changed. + +Because of the way NSSM registers itself you should be aware that you may not +be able to replace the NSSM binary if you have the event viewer open and that +running multiple instances of NSSM from different locations may be confusing if +they are not all the same version. + + +Example usage +------------- +To install an Unreal Tournament server: + + nssm install UT2004 c:\games\ut2004\system\ucc.exe server + +To run the server as the "games" user: + + nssm set UT2004 ObjectName games password + +To configure the server to log to a file: + + nssm set UT2004 AppStdout c:\games\ut2004\service.log + +To restrict the server to a single CPU: + + nssm set UT2004 AppAffinity 0 + +To remove the server: + + nssm remove UT2004 confirm + +To find out the service name of a service with a display name: + + nssm get "Background Intelligent Transfer Service" Name + + +Building NSSM from source +------------------------- +NSSM is known to compile with Visual Studio 2008 and later. Older Visual +Studio releases may or may not work if you install an appropriate SDK and +edit the nssm.vcproj and nssm.sln files to set a lower version number. +They are known not to work with default settings. + +NSSM will also compile with Visual Studio 2010 but the resulting executable +will not run on versions of Windows older than XP SP2. If you require +compatiblity with older Windows releases you should change the Platform +Toolset to v90 in the General section of the project's Configuration +Properties. + + +Credits +------- +Thanks to Bernard Loh for finding a bug with service recovery. +Thanks to Benjamin Mayrargue (www.softlion.com) for adding 64-bit support. +Thanks to Joel Reingold for spotting a command line truncation bug. +Thanks to Arve Knudsen for spotting that child processes of the monitored +application could be left running on service shutdown, and that a missing +registry value for AppDirectory confused NSSM. +Thanks to Peter Wagemans and Laszlo Keresztfalvi for suggesting throttling +restarts. +Thanks to Eugene Lifshitz for finding an edge case in CreateProcess() and for +advising how to build messages.mc correctly in paths containing spaces. +Thanks to Rob Sharp for pointing out that NSSM did not respect the +AppEnvironment registry value used by srvany. +Thanks to Szymon Nowak for help with Windows 2000 compatibility. +Thanks to François-Régis Tardy and Gildas le Nadan for French translation. +Thanks to Emilio Frini for spotting that French was inadvertently set as +the default language when the user's display language was not translated. +Thanks to Riccardo Gusmeroli and Marco Certelli for Italian translation. +Thanks to Eric Cheldelin for the inspiration to generate a Control-C event +on shutdown. +Thanks to Brian Baxter for suggesting how to escape quotes from the command +prompt. +Thanks to Russ Holmann for suggesting that the shutdown timeout be configurable. +Thanks to Paul Spause for spotting a bug with default registry entries. +Thanks to BUGHUNTER for spotting more GUI bugs. +Thanks to Doug Watson for suggesting file rotation. +Thanks to Арслан Сайдуганов for suggesting setting process priority. +Thanks to Robert Middleton for suggestion and draft implementation of process +affinity support. +Thanks to Andrew RedzMax for suggesting an unconditional restart delay. +Thanks to Bryan Senseman for noticing that applications with redirected stdout +and/or stderr which attempt to read from stdin would fail. +Thanks to Czenda Czendov for help with Visual Studio 2013 and Server 2012R2. +Thanks to Alessandro Gherardi for reporting and draft fix of the bug whereby +the second restart of the application would have a corrupted environment. +Thanks to Hadrien Kohl for suggesting to disable the console window's menu. +Thanks to Allen Vailliencourt for noticing bugs with configuring the service to +run under a local user account. +Thanks to Sam Townsend for noticing a regression with TerminateProcess(). + +Licence +------- +NSSM is public domain. You may unconditionally use it and/or its source code +for any purpose you wish. diff --git a/context-windows/nssm-2.24/win32/nssm.exe b/context-windows/nssm-2.24/win32/nssm.exe new file mode 100644 index 00000000..8faee45b Binary files /dev/null and b/context-windows/nssm-2.24/win32/nssm.exe differ diff --git a/context-windows/nssm-2.24/win64/nssm.exe b/context-windows/nssm-2.24/win64/nssm.exe new file mode 100644 index 00000000..6ccfe3cf Binary files /dev/null and b/context-windows/nssm-2.24/win64/nssm.exe differ diff --git a/context-windows/package.wxs b/context-windows/package.wxs new file mode 100644 index 00000000..02d8c975 --- /dev/null +++ b/context-windows/package.wxs @@ -0,0 +1,173 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + INSTALLED OR NOT NEWERVERSIONDETECTED + + + + + + INSTALLED OR PSEXE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NOT REMOVE AND FASTBOOT + + + + diff --git a/context-windows/src/context.ps1 b/context-windows/src/context.ps1 new file mode 100644 index 00000000..0dd55141 --- /dev/null +++ b/context-windows/src/context.ps1 @@ -0,0 +1,1408 @@ +# -------------------------------------------------------------------------- # +# Copyright 2002-2021, OpenNebula Project, OpenNebula Systems # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); you may # +# not use this file except in compliance with the License. You may obtain # +# a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +#--------------------------------------------------------------------------- # + +# Original work by: + +################################################################# +##### Windows Powershell Script to configure OpenNebula VMs ##### +##### Created by andremonteiro@ua.pt and tsbatista@ua.pt ##### +##### DETI/IEETA Universidade de Aveiro 2011 ##### +################################################################# + +################################################################################ +# Functions +################################################################################ + +function logmsg($message) { + # powershell 4 does not automatically add newline in the transcript so we + # workaround it by adding it explicitly and using the NoNewline argument + # we ensure that it will not be added twice + Write-Host "[$(Get-Date -Format 'yyyy-MM-dd HH:mm K')] $message`r`n" -NoNewline +} + +function logsuccess { + logmsg " ... Success" +} +function logfail { + logmsg " ... Failed" +} + +function getContext($file) { + + # TODO: Improve regexp for multiple SSH keys on SSH_PUBLIC_KEY + $context = @{} + switch -regex -file $file { + "^([^=]+)='(.+?)'$" { + $name, $value = $matches[1..2] + $context[$name] = $value + } + } + return $context +} + +function envContext($context) { + ForEach ($h in $context.GetEnumerator()) { + $name = "Env:" + $h.Name + Set-Item $name $h.Value + } +} + +function contextChanged($file, $last_checksum) { + $new_checksum = Get-FileHash -Algorithm SHA256 $file + $ret = $last_checksum.Hash -ne $new_checksum.Hash + return $ret +} + +function waitForContext($checksum) { + # This object will be set and returned at the end + $contextPaths = New-Object PsObject -Property @{ + contextScriptPath = $null + contextPath = $null + contextDrive = $null + contextLetter = $null + contextInitScriptPath = $null + } + + # How long to wait before another poll (in seconds) + $sleep = 30 + + do { + + # Reset the contextPath + $contextPaths.contextPath = "" + + # Get all drives and select only the one that has "CONTEXT" as a label + $contextPaths.contextDrive = Get-WMIObject Win32_Volume | Where-Object { $_.Label -eq "CONTEXT" } + + if ($contextPaths.contextDrive) { + + # At this point we can obtain the letter of the contextDrive + $contextPaths.contextLetter = $contextPaths.contextDrive.Name + $contextPaths.contextPath = $contextPaths.contextLetter + "context.sh" + $contextPaths.contextInitScriptPath = $contextPaths.contextLetter + } + else { + + # Try the VMware API + foreach ($pf in ${env:ProgramFiles}, ${env:ProgramFiles(x86)}, ${env:ProgramW6432}) { + $vmtoolsd = "${pf}\VMware\VMware Tools\vmtoolsd.exe" + if (Test-Path $vmtoolsd) { + break + } + } + + $vmwareContext = "" + if (Test-Path $vmtoolsd) { + $vmwareContext = & $vmtoolsd --cmd "info-get guestinfo.opennebula.context" | Out-String + } + + if ("$vmwareContext" -ne "") { + [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($vmwareContext)) | Out-File "$ctxDir\context.sh" "UTF8" + $contextPaths.contextLetter = $env:SystemDrive + "\" + $contextPaths.contextPath = "$ctxDir\context.sh" + $contextPaths.contextInitScriptPath = "$ctxDir\.init-scripts\" + + if (!(Test-Path $contextPaths.contextInitScriptPath)) { + mkdir $contextPaths.contextInitScriptPath + } + + # Look for INIT_SCRIPTS + $fileId = 0 + while ($true) { + $vmwareInitFilename = & $vmtoolsd --cmd "info-get guestinfo.opennebula.file.${fileId}" | Select-Object -First 1 | Out-String + + $vmwareInitFilename = $vmwareInitFilename.Trim() + + if ($vmwareInitFilename -eq "") { + # no file found + break + } + + $vmwareInitFileContent64 = & $vmtoolsd --cmd "info-get guestinfo.opennebula.file.${fileId}" | Select-Object -Skip 1 | Out-String + + # Sanitize the filenames (drop any path from them and instead use our directory) + $vmwareInitFilename = $contextPaths.contextInitScriptPath + [System.IO.Path]::GetFileName("$vmwareInitFilename") + + [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($vmwareInitFileContent64)) | Out-File "${vmwareInitFilename}" "UTF8" + + $fileId++ + } + } + + } + + # Terminate the wait-loop only when context.sh is found and changed + if (![string]::IsNullOrEmpty($contextPaths.contextPath) -and (Test-Path $contextPaths.contextPath)) { + + # Context must differ + if (contextChanged $contextPaths.contextPath $checksum) { + Break + } + } + + cleanup $contextPaths + + Write-Host "`r`n" -NoNewline + Start-Sleep -Seconds $sleep + } while ($true) + + # make a copy of the context.sh in the case another event would happen and + # trigger a new context.sh while still working on the previous one which + # would result in a mismatched checksum... + $contextPaths.contextScriptPath = "$ctxDir\.opennebula-context.sh" + Copy-Item -Path $contextPaths.contextPath -Destination $contextPaths.contextScriptPath -Force + + return $contextPaths +} + +function addLocalUser($context) { + # Create new user + $username = $context["USERNAME"] + $password = $context["PASSWORD"] + $password64 = $context["PASSWORD_BASE64"] + + If ($password64) { + $password = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($password64)) + } + + if ($username -Or $password) { + + if ($null -eq $username) { + # ATTENTION - Language/Regional settings have influence on the naming + # of this user. Use the User SID instead (S-1-5-21domain-500) + $username = (Get-WmiObject -Class "Win32_UserAccount" | + Where-Object { $_.SID -like "S-1-5-21[0-9-]*-500" } | + Select-Object -ExpandProperty Name | + Get-Unique -AsString) + } + + logmsg "* Creating Account for $username" + + $ADSI = [adsi]$ConnectionString + + if (!([ADSI]::Exists("WinNT://$computerName/$username"))) { + # User does not exist, Create the User + logmsg "- Creating account" + $user = $ADSI.Create("user", $username) + $user.setPassword($password) + $user.SetInfo() + } + else { + # User exists, Set Password + logmsg "- Setting Password" + $admin = [ADSI]"WinNT://$env:computername/$username" + $admin.psbase.invoke("SetPassword", $password) + } + + # Set Password to Never Expire + logmsg "- Setting password to never expire" + $admin = [ADSI]"WinNT://$env:computername/$username" + $admin.UserFlags.value = $admin.UserFlags.value -bor 0x10000 + $admin.CommitChanges() + + # Add user to local Administrators + # ATTENTION - Language/Regional settings have influence on the naming + # of this group. Use the Group SID instead (S-1-5-32-544) + $groups = (Get-WmiObject -Class "Win32_Group" | + Where-Object { $_.SID -like "S-1-5-32-544" } | + Select-Object -ExpandProperty Name) + + ForEach ($grp in $groups) { + + # Make sure the Group exists + If ([ADSI]::Exists("WinNT://$computerName/$grp,group")) { + + # Check if the user is a Member of the Group + $group = [ADSI] "WinNT://$computerName/$grp,group" + $members = @($group.psbase.Invoke("Members")) + + $memberNames = @() + $members | ForEach-Object { + # https://p0w3rsh3ll.wordpress.com/2016/06/14/any-documented-adsi-changes-in-powershell-5-0/ + $memberNames += ([ADSI]$_).psbase.InvokeGet('Name') + } + + If (-Not ($memberNames -Contains $username)) { + + # Make sure the user exists, again + if ([ADSI]::Exists("WinNT://$computerName/$username")) { + + # Add the user + logmsg "- Adding to $grp" + $group.Add("WinNT://$computerName/$username") + } + } + } + } + } + Write-Host "`r`n" -NoNewline +} + +function configureNetwork($context) { + + # Get the NIC in the Context + $nicIds = ($context.Keys | Where-Object { $_ -match '^ETH\d+_MAC$' } | ForEach-Object { $_ -replace '(^ETH|_MAC$)', '' } | Sort-Object -Unique) + + $nicId = 0 + + foreach ($nicId in $nicIds) { + $nicPrefix = "ETH" + $nicId + "_" + + $method = $context[$nicPrefix + 'METHOD'] + $ip = $context[$nicPrefix + 'IP'] + $netmask = $context[$nicPrefix + 'MASK'] + $mac = $context[$nicPrefix + 'MAC'] + $dns = (($context[$nicPrefix + 'DNS'] -split " " | Where-Object { $_ -match '^(([0-9]*).?){4}$' }) -join ' ') + $dns6 = (($context[$nicPrefix + 'DNS'] -split " " | Where-Object { $_ -match '^(([0-9A-F]*):?)*$' }) -join ' ') + $dnsSuffix = $context[$nicPrefix + 'SEARCH_DOMAIN'] + $gateway = $context[$nicPrefix + 'GATEWAY'] + $network = $context[$nicPrefix + 'NETWORK'] + $mtu = $context[$nicPrefix + 'MTU'] + $metric = $context[$nicPrefix + 'METRIC'] + + $ip6Method = $context[$nicPrefix + 'IP6_METHOD'] + $ip6 = $context[$nicPrefix + 'IP6'] + $ip6ULA = $context[$nicPrefix + 'IP6_ULA'] + $ip6Prefix = $context[$nicPrefix + 'IP6_PREFIX_LENGTH'] + $ip6Gw = $context[$nicPrefix + 'IP6_GATEWAY'] + $ip6Metric = $context[$nicPrefix + 'IP6_METRIC'] + + $mac = $mac.ToUpper() + if (!$netmask) { + $netmask = "255.255.255.0" + } + if (!$ip6Prefix) { + $ip6Prefix = "64" + } + if (!$ip6Gw) { + # Backward compatibility, new context parameter + # ETHx_IP6_GATEWAY introduced since 6.2 + $ip6Gw = $context[$nicPrefix + 'GATEWAY6'] + } + if (!$ip6Metric) { + $ip6Metric = $metric + } + if (!$network) { + $network = $ip -replace "\.[^.]+$", ".0" + } + if ($nicId -eq 0 -and !$gateway) { + $gateway = $ip -replace "\.[^.]+$", ".1" + } + + # default NIC configuration methods + if (!$method) { + $method = 'static' + } + if (!$ip6Method) { + $ip6Method = $method + } + + # Load the NIC Configuration Object + $nic = $false + $retry = 30 + do { + $retry-- + Start-Sleep -s 1 + $nic = Get-WMIObject Win32_NetworkAdapterConfiguration | ` + Where-Object { $_.IPEnabled -eq "TRUE" -and $_.MACAddress -eq $mac } + } while (!$nic -and $retry) + + If (!$nic) { + logmsg ("* Configuring Network Settings: " + $mac) + logmsg (" ... Failed: Interface with MAC not found") + Continue + } + + # We need the connection ID (i.e. "Local Area Connection", + # which can be discovered from the NetworkAdapter object + $na = Get-WMIObject Win32_NetworkAdapter | ` + Where-Object { $_.deviceId -eq $nic.index } + + If (!$na) { + logmsg ("* Configuring Network Settings: " + $mac) + logmsg (" ... Failed: Network Adapter not found") + Continue + } + + logmsg ("* Configuring Network Settings: " + $nic.Description.ToString()) + + # Flag to indicate if any IPv4/6 configuration was placed + $set_ip_conf = $false + + # IPv4 Configuration Methods + Switch -Regex ($method) { + '^\s*static\s*$' { + if ($ip) { + # Release the DHCP lease, will fail if adapter not DHCP Configured + logmsg "- Release DHCP Lease" + $ret = $nic.ReleaseDHCPLease() + If ($ret.ReturnValue) { + logmsg (" ... Failed: " + $ret.ReturnValue.ToString()) + } + Else { + logmsg " ... Success" + } + + # set static IP address and retry for few times if there was a problem + # with acquiring write lock (2147786788) for network configuration + # https://msdn.microsoft.com/en-us/library/aa390383(v=vs.85).aspx + logmsg "- Set Static IP" + $retry = 10 + do { + $retry-- + Start-Sleep -s 1 + $ret = $nic.EnableStatic($ip , $netmask) + } while ($ret.ReturnValue -eq 2147786788 -and $retry) + If ($ret.ReturnValue) { + logmsg (" ... Failed: " + $ret.ReturnValue.ToString()) + } + Else { + logmsg " ... Success" + } + + # Set IPv4 MTU + if ($mtu) { + logmsg "- Set MTU: ${mtu}" + netsh interface ipv4 set interface $nic.InterfaceIndex mtu=$mtu + + If ($?) { + logmsg " ... Success" + } + Else { + logmsg " ... Failed" + } + } + + # Set the Gateway + if ($gateway) { + if ($metric) { + logmsg "- Set Gateway with metric" + $ret = $nic.SetGateways($gateway, $metric) + } + Else { + logmsg "- Set Gateway" + $ret = $nic.SetGateways($gateway) + } + + If ($ret.ReturnValue) { + logmsg (" ... Failed: " + $ret.ReturnValue.ToString()) + } + Else { + logmsg " ... Success" + } + } + + # Set DNS servers + If ($dns) { + $dnsServers = $dns -split " " + + # DNS Server Search Order + logmsg "- Set DNS Server Search Order" + $ret = $nic.SetDNSServerSearchOrder($dnsServers) + If ($ret.ReturnValue) { + logmsg (" ... Failed: " + $ret.ReturnValue.ToString()) + } + Else { + logmsg " ... Success" + } + + # Set Dynamic DNS Registration + logmsg "- Set Dynamic DNS Registration" + $ret = $nic.SetDynamicDNSRegistration("TRUE") + If ($ret.ReturnValue) { + logmsg (" ... Failed: " + $ret.ReturnValue.ToString()) + } + Else { + logmsg " ... Success" + } + + # WINS Addresses + # $nic.SetWINSServer($DNSServers[0], $DNSServers[1]) + } + + # Set DNS domain/search order + if ($dnsSuffix) { + $dnsSuffixes = $dnsSuffix -split " " + + # Set DNS Suffix Search Order + logmsg "- Set DNS Suffix Search Order" + $ret = ([WMIClass]"Win32_NetworkAdapterConfiguration").SetDNSSuffixSearchOrder(($dnsSuffixes)) + If ($ret.ReturnValue) { + logmsg (" ... Failed: " + $ret.ReturnValue.ToString()) + } + Else { + logmsg " ... Success" + } + + # Set Primary DNS Domain + logmsg "- Set Primary DNS Domain" + $ret = $nic.SetDNSDomain($dnsSuffixes[0]) + If ($ret.ReturnValue) { + logmsg (" ... Failed: " + $ret.ReturnValue.ToString()) + } + Else { + logmsg " ... Success" + } + } + + $set_ip_conf = $true + } + else { + logmsg "- No static IPv4 configuration provided, skipping" + } + } + + '^\s*dhcp\s*$' { + # Enable DHCP + logmsg "- Enable DHCP" + $ret = $nic.EnableDHCP() + # TODO: 1 ... Successful completion, reboot required + If ($ret.ReturnValue) { + logmsg (" ... Failed: " + $ret.ReturnValue.ToString()) + } + Else { + logmsg " ... Success" + } + + # Set IPv4 MTU + if ($mtu) { + logmsg "- Set MTU: ${mtu}" + netsh interface ipv4 set interface $nic.InterfaceIndex mtu=$mtu + + If ($?) { + logmsg " ... Success" + } + Else { + logmsg " ... Failed" + } + } + + $set_ip_conf = $true + } + + '\s*skip\s*$' { + logmsg "- Skipped IPv4 configuration as requested in method (${nicPrefix}METHOD=${method})" + } + + Default { + logmsg "- Unknown IPv4 method (${nicPrefix}METHOD=${method}), skipping configuration" + } + } + + # IPv6 Configuration Methods + Switch -Regex ($ip6Method) { + '^\s*static\s*$' { + if ($ip6) { + enableIPv6 + disableIPv6Privacy + + # Disable router discovery + logmsg "- Disable IPv6 router discovery" + netsh interface ipv6 set interface $na.NetConnectionId ` + advertise=disabled routerdiscover=disabled | Out-Null + + If ($?) { + logmsg " ... Success" + } + Else { + logmsg " ... Failed" + } + + # Remove old IPv6 addresses + logmsg "- Removing old IPv6 addresses" + if (Get-Command Remove-NetIPAddress -ErrorAction SilentlyContinue) { + # Windows 8.1 and Server 2012 R2 and up + # we want to remove everything except the link-local address + Remove-NetIPAddress -InterfaceAlias $na.NetConnectionId ` + -AddressFamily IPv6 -Confirm:$false ` + -PrefixOrigin Other, Manual, Dhcp, RouterAdvertisement ` + -errorAction SilentlyContinue + + If ($?) { + logmsg " ... Success" + } + Else { + logmsg " ... Nothing to do" + } + } + Else { + logmsg " ... Not implemented" + } + + # Set IPv6 Address + logmsg "- Set IPv6 Address" + netsh interface ipv6 add address $na.NetConnectionId $ip6/$ip6Prefix + If ($? -And $ip6ULA) { + netsh interface ipv6 add address $na.NetConnectionId $ip6ULA/64 + } + + If ($?) { + logmsg " ... Success" + } + Else { + logmsg " ... Failed" + } + + # Set IPv6 Gateway + if ($ip6Gw) { + if ($ip6Metric) { + logmsg "- Set IPv6 Gateway with metric" + netsh interface ipv6 add route ::/0 $na.NetConnectionId $ip6Gw metric="${ip6Metric}" + } + else { + logmsg "- Set IPv6 Gateway" + netsh interface ipv6 add route ::/0 $na.NetConnectionId $ip6Gw + } + + If ($?) { + logmsg " ... Success" + } + Else { + logmsg " ... Failed" + } + } + + # Set IPv6 MTU + if ($mtu) { + logmsg "- Set IPv6 MTU: ${mtu}" + netsh interface ipv6 set interface $nic.InterfaceIndex mtu=$mtu + + If ($?) { + logmsg " ... Success" + } + Else { + logmsg " ... Failed" + } + } + + # Remove old IPv6 DNS Servers + logmsg "- Removing old IPv6 DNS Servers" + netsh interface ipv6 set dnsservers $na.NetConnectionId source=static address= + + If ($dns6) { + # Set IPv6 DNS Servers + logmsg "- Set IPv6 DNS Servers" + $dns6Servers = $dns6 -split " " + foreach ($dns6Server in $dns6Servers) { + netsh interface ipv6 add dnsserver $na.NetConnectionId address=$dns6Server + } + } + + $set_ip_conf = $true + + doPing($ip6) + } + else { + logmsg "- No static IPv6 configuration provided, skipping" + } + } + + '^\s*(auto|dhcp)\s*$' { + enableIPv6 + disableIPv6Privacy + + # Enable router discovery + logmsg "- Enable IPv6 router discovery" + netsh interface ipv6 set interface $na.NetConnectionId ` + advertise=disabled routerdiscover=enabled | Out-Null + + # Run of DHCPv6 client is controlled by RA managed/other + # flags, we can't independently enable/disable DHCPv6 + # client. So at least we release the address allocated + # through DHCPv6 in auto mode. See + # https://serverfault.com/questions/692291/disable-dhcpv6-client-in-windows + if ($ip6Method -match '^\s*auto\s*$') { + logmsg "- Release DHCPv6 Lease (selected method auto, not dhcp!)" + ipconfig /release6 $na.NetConnectionId + + If ($?) { + logmsg " ... Success" + } + Else { + logmsg " ... Failed" + } + } + + # Set IPv6 MTU + if ($mtu) { + logmsg "- Set IPv6 MTU: ${mtu}" + logmsg "WARNING: MTU will be overwritten if announced as part of RA!" + netsh interface ipv6 set interface $nic.InterfaceIndex mtu=$mtu + + If ($?) { + logmsg " ... Success" + } + Else { + logmsg " ... Failed" + } + } + + $set_ip_conf = $true + } + + '^\s*disable\s*$' { + disableIPv6 + } + + '\s*skip\s*$' { + logmsg "- Skipped IPv6 configuration as requested in method (${nicPrefix}IP6_METHOD=${ip6Method})" + } + + Default { + logmsg "- Unknown IPv6 method (${nicPrefix}IP6_METHOD=${ip6Method}), skipping configuration" + } + } + + ### + + # If no IP configuration happened, we skip + # configuring additional IP addresses (aliases) + If ($set_ip_conf -eq $false) { + logmsg "- Skipped IP aliases configuration due to missing main IP" + Continue + } + + # Get the aliases for the NIC in the Context + $aliasIds = ($context.Keys | Where-Object { $_ -match "^ETH${nicId}_ALIAS\d+_IP6?$" } | ForEach-Object { $_ -replace '(^ETH\d+_ALIAS|_IP$|_IP6$)', '' } | Sort-Object -Unique) + + foreach ($aliasId in $aliasIds) { + $aliasPrefix = "ETH${nicId}_ALIAS${aliasId}" + $aliasIp = $context[$aliasPrefix + '_IP'] + $aliasNetmask = $context[$aliasPrefix + '_MASK'] + $aliasIp6 = $context[$aliasPrefix + '_IP6'] + $aliasIp6ULA = $context[$aliasPrefix + '_IP6_ULA'] + $aliasIp6Prefix = $context[$aliasPrefix + '_IP6_PREFIX_LENGTH'] + $detach = $context[$aliasPrefix + '_DETACH'] + $external = $context[$aliasPrefix + '_EXTERNAL'] + + if ($external -and ($external -eq "YES")) { + continue + } + + if (!$aliasNetmask) { + $aliasNetmask = "255.255.255.0" + } + + if (!$aliasIp6Prefix) { + $aliasIp6Prefix = "64" + } + + if ($aliasIp -and !$detach) { + logmsg "- Set Additional Static IP (${aliasPrefix})" + netsh interface ipv4 add address $nic.InterfaceIndex $aliasIp $aliasNetmask + + If ($?) { + logmsg " ... Success" + } + Else { + logmsg " ... Failed" + } + } + + if ($aliasIp6 -and !$detach) { + logmsg "- Set Additional IPv6 Address (${aliasPrefix})" + netsh interface ipv6 add address $nic.InterfaceIndex $aliasIp6/$aliasIp6Prefix + If ($? -And $aliasIp6ULA) { + netsh interface ipv6 add address $nic.InterfaceIndex $aliasIp6ULA/64 + } + + If ($?) { + logmsg " ... Success" + } + Else { + logmsg " ... Failed" + } + } + } + + If ($ip) { + doPing($ip) + } + } + + Write-Host "`r`n" -NoNewline +} + +function setTimeZone($context) { + $timezone = $context['TIMEZONE'] + + If ($timezone) { + logmsg "* Configuring time zone '${timezone}'" + + tzutil /s "${timezone}" + + If ($?) { + logmsg ' ... Success' + } + Else { + logmsg ' ... Failed' + } + } +} + +function renameComputer($context) { + # Initialize Variables + $current_hostname = hostname + $context_hostname = $context["SET_HOSTNAME"] + + # SET_HOSTNAME was not set but maybe DNS_HOSTNAME was... + if (! $context_hostname) { + $dns_hostname = $context["DNS_HOSTNAME"] + + if ($null -ne $dns_hostname -and $dns_hostname.ToUpper() -eq "YES") { + + # we will set our hostname based on the reverse dns lookup - the IP + # in question is the first one with a set default gateway + # (as is done by get_first_ip in addon-context-linux) + + logmsg "* Requested change of Hostname via reverse DNS lookup (DNS_HOSTNAME=YES)" + $first_ip = (Get-WmiObject -Class Win32_NetworkAdapterConfiguration | Where-Object { $null -ne $_.DefaultIPGateway }).IPAddress | Select-Object -First 1 + $context_hostname = [System.Net.Dns]::GetHostbyAddress($first_ip).HostName + logmsg "- Resolved Hostname is: $context_hostname" + } + Else { + + # no SET_HOSTNAME nor DNS_HOSTNAME - skip setting hostname + return + } + } + + $splitted_hostname = $context_hostname.split('.') + $context_hostname = $splitted_hostname[0] + $context_domain = $splitted_hostname[1..$splitted_hostname.length] -join '.' + + If ($context_domain) { + logmsg "* Changing Domain to $context_domain" + + $networkConfig = Get-WmiObject Win32_NetworkAdapterConfiguration -filter "ipenabled = 'true'" + $ret = $networkConfig.SetDnsDomain($context_domain) + + If ($ret.ReturnValue) { + + # Returned Non Zero, Failed, No restart + logmsg (" ... Failed: " + $ret.ReturnValue.ToString()) + } + Else { + + # Returned Zero, Success + logmsg " ... Success" + } + } + + # Check for the .opennebula-renamed file + $logged_hostname = "" + If (Test-Path "$ctxDir\.opennebula-renamed") { + logmsg "- Using the JSON file: $ctxDir\.opennebula-renamed" + + # Grab the JSON content + $json = Get-Content -Path "$ctxDir\.opennebula-renamed" ` + | Out-String + + # Convert to a Hash Table and set the Logged Hostname + try { + $status = $json | ConvertFrom-Json + $logged_hostname = $status.ComputerName + } + # Invalid JSON + catch [System.ArgumentException] { + logmsg " [!] Invalid JSON:" + Write-Host $json.ToString() + } + } + Else { + + # no renaming was ever done - we fallback to our current Hostname + $logged_hostname = $current_hostname + } + + If (($current_hostname -ne $context_hostname) -and ` + ($context_hostname -eq $logged_hostname)) { + + # avoid rename->reboot loop - if we detect that rename attempt was done + # but failed then we drop log message about it and finish... + + logmsg "* Computer Rename Attempted but failed:" + logmsg "- Current: $current_hostname" + logmsg "- Context: $context_hostname" + } + ElseIf ($context_hostname -ne $current_hostname) { + + # the current_name does not match the context_name, rename the computer + + logmsg "* Changing Hostname to $context_hostname" + # Load the ComputerSystem Object + $ComputerInfo = Get-WmiObject -Class Win32_ComputerSystem + + # Rename the computer + $ret = $ComputerInfo.rename($context_hostname) + + $contents = @{} + $contents["ComputerName"] = $context_hostname + ConvertTo-Json $contents | Out-File "$ctxDir\.opennebula-renamed" + + # Check success + If ($ret.ReturnValue) { + + # Returned Non Zero, Failed, No restart + logmsg (" ... Failed: " + $ret.ReturnValue.ToString()) + Write-Host " Check the computername. " + Write-Host "Possible Issues: The name cannot include control " ` + "characters, leading or trailing spaces, or any of " ` + "the following characters: `" / \ [ ] : | < > + = ; , ?" + + } + Else { + + # Returned Zero, Success + logmsg " ... Success" + + # Restart the Computer + logmsg " ... Rebooting" + Restart-Computer -Force + + # Exit here so the script doesn't continue to run + Exit 0 + } + } + Else { + + # Hostname is set and correct + logmsg "* Computer Name already set: $context_hostname" + } + + Write-Host "`r`n" -NoNewline +} + +function enableRemoteDesktop() { + logmsg "* Enabling Remote Desktop" + # Windows 7 only - add firewall exception for RDP + logmsg "- Enable Remote Desktop Rule Group" + netsh advfirewall Firewall set rule group="Remote Desktop" new enable=yes + + # Enable RDP + logmsg "- Enable Allow Terminal Services Connections" + $ret = (Get-WmiObject -Class "Win32_TerminalServiceSetting" -Namespace root\cimv2\terminalservices).SetAllowTsConnections(1) + If ($ret.ReturnValue) { + logmsg (" ... Failed: " + $ret.ReturnValue.ToString()) + } + Else { + logmsg " ... Success" + } + Write-Host "`r`n" -NoNewline +} + +function enablePing() { + logmsg "* Enabling Ping" + #Create firewall manager object + New-Object -com hnetcfg.fwmgr + + # Get current profile + $pro = $fwmgcalPolicy.CurrentProfile + + logmsg "- Enable Allow Inbound Echo Requests" + $ret = $pro.IcmpSettings.AllowInboundEchoRequest = $true + If ($ret) { + logmsg " ... Success" + } + Else { + logmsg " ... Failed" + } + + Write-Host "`r`n" -NoNewline +} + +function doPing($ip, $retries = 20) { + logmsg "- Ping Interface IP $ip" + + $ping = $false + $retry = 0 + do { + $retry++ + Start-Sleep -s 1 + $ping = Test-Connection -ComputerName $ip -Count 1 -Quiet -ErrorAction SilentlyContinue + } while (!$ping -and ($retry -lt $retries)) + + If ($ping) { + logmsg " ... Success ($retry tries)" + } + Else { + logmsg " ... Failed ($retry tries)" + } +} + +function disableIPv6Privacy() { + # Disable Randomization of IPv6 addresses (use EUI-64) + logmsg "- Globally disable IPv6 Identifiers Randomization" + netsh interface ipv6 set global randomizeidentifiers=disable + + If ($?) { + logmsg " ... Success" + } + Else { + logmsg " ... Failed" + } + + # Disable IPv6 Privacy Extensions (temporary addresses) + logmsg "- Globally disable IPv6 Privacy Extensions" + netsh interface ipv6 set privacy state=disabled + + If ($?) { + logmsg " ... Success" + } + Else { + logmsg " ... Failed" + } +} + +function enableIPv6() { + logmsg '- Enabling IPv6' + + Enable-NetAdapterBinding -Name $na.NetConnectionId -ComponentID ms_tcpip6 + + If ($?) { + logmsg " ... Success" + } + Else { + logmsg " ... Failed" + } +} + +function disableIPv6() { + logmsg '- Disabling IPv6' + + Disable-NetAdapterBinding -Name $na.NetConnectionId -ComponentID ms_tcpip6 + + If ($?) { + logmsg " ... Success" + } + Else { + logmsg " ... Failed" + } +} + +function runScripts($context, $contextPaths) { + logmsg "* Running Scripts" + + # Get list of scripts to run, " " delimited + $initscripts = $context["INIT_SCRIPTS"] + + if ($initscripts) { + # Parse each script and run it + ForEach ($script in $initscripts.split(" ")) { + + # Sanitize the filename (drop any path from them and instead use our directory) + $script = $contextPaths.contextInitScriptPath + [System.IO.Path]::GetFileName($script.Trim()) + + if (Test-Path $script) { + logmsg "- $script" + envContext($context) + pswrapper "$script" + } + + } + } + else { + # Emulate the init.sh fallback behavior from Linux + $script = $contextPaths.contextInitScriptPath + "init.ps1" + + if (Test-Path $script) { + logmsg "- $script" + envContext($context) + pswrapper "$script" + } + } + + # Execute START_SCRIPT or START_SCRIPT_64 + $startScript = $context["START_SCRIPT"] + $startScript64 = $context["START_SCRIPT_BASE64"] + + if ($startScript64) { + $startScript = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($startScript64)) + } + + if ($startScript) { + + # Save the script as .opennebula-startscript.ps1 + $startScriptPS = "$ctxDir\.opennebula-startscript.ps1" + $startScript | Out-File $startScriptPS "UTF8" + + # Launch the Script + logmsg "- $startScriptPS" + envContext($context) + pswrapper "$startScriptPS" + removeFile "$startScriptPS" + } + Write-Host "`r`n" -NoNewline +} + +function extendPartition($disk, $part) { + "select disk $disk", "select partition $part", "extend" | diskpart | Out-Null +} + +function extendPartitions($context) { + logmsg "* Extend partitions" + + "rescan" | diskpart + + $disks = @() + + # Cmdlet 'Get-Partition' is not in older Windows/Powershell versions + if (Get-Command -ErrorAction SilentlyContinue -Name Get-Partition) { + if ([string]$context['GROW_ROOTFS'] -eq '' -or $context['GROW_ROOTFS'].ToUpper() -eq 'YES') { + # Add at least C: + $drives = "C: $($context['GROW_FS'])" + } + else { + $drives = "$($context['GROW_FS'])" + } + + $driveLetters = (-split $drives | Select-String -Pattern "^(\w):?[\/]?$" -AllMatches | ForEach-Object { $_.matches.groups[1].Value } | Sort-Object -Unique) + + ForEach ($driveLetter in $driveLetters) { + $disk = New-Object PsObject -Property @{ + name = $null + diskId = $null + partIds = @() + } + # TODO: in the future an AccessPath can be used instead of just DriveLetter + $drive = (Get-Partition -DriveLetter $driveLetter) + $disk.name = "$driveLetter" + ':' + $disk.diskId = $drive.DiskNumber + $disk.partIds += $drive.PartitionNumber + $disks += $disk + } + } + Else { + # always resize at least the disk 0 + $disk = New-Object PsObject -Property @{ + name = $null + diskId = 0 + partIds = @() + } + + # select all parts - preserve old behavior for disk 0 + $disk.partIds = "select disk $($disk.diskId)", "list partition" | diskpart | Select-String -Pattern "^\s+\w+ (\d+)\s+" -AllMatches | ForEach-Object { $_.matches.groups[1].Value } + $disks += $disk + } + + # extend all requested disk/part + ForEach ($disk in $disks) { + ForEach ($partId in $disk.partIds) { + if ($disk.name) { + logmsg "- Extend ($($disk.name)) Disk: $($disk.diskId) / Part: $partId" + } + Else { + logmsg "- Extend Disk: $($disk.diskId) / Part: $partId" + } + extendPartition $disk.diskId $partId + } + } +} + +function reportReady($context, $contextLetter) { + $reportReady = $context['REPORT_READY'] + $oneGateEndpoint = $context['ONEGATE_ENDPOINT'] + $vmId = $context['VMID'] + $token = $context['ONEGATE_TOKEN'] + $retryCount = 3 + $retryWaitPeriod = 10 + + if ($reportReady -and $reportReady.ToUpper() -eq 'YES') { + logmsg '* Report Ready to OneGate' + + if (!$oneGateEndpoint) { + logmsg ' ... Failed: ONEGATE_ENDPOINT not set' + return + } + + if (!$vmId) { + logmsg ' ... Failed: VMID not set' + return + } + + if (!$token) { + logmsg " ... Token not set. Try file" + $tokenPath = $contextLetter + 'token.txt' + if (Test-Path $tokenPath) { + $token = Get-Content $tokenPath + } + else { + logmsg " ... Failed: Token file not found" + return + } + } + + $retryNumber = 1 + while ($true) { + try { + $body = 'READY=YES' + $target = $oneGateEndpoint + '/vm' + + [System.Net.HttpWebRequest] $webRequest = [System.Net.WebRequest]::Create($target) + $webRequest.Timeout = 10000 + $webRequest.Method = 'PUT' + $webRequest.Headers.Add('X-ONEGATE-TOKEN', $token) + $webRequest.Headers.Add('X-ONEGATE-VMID', $vmId) + $buffer = [System.Text.Encoding]::UTF8.GetBytes($body) + $webRequest.ContentLength = $buffer.Length + + if ($oneGateEndpoint -ilike "https://*") { + #For reporting on HTTPS OneGateEndpoint + logmsg " ... Use HTTPS for OneGateEndpoint report: $oneGateEndpoint" + $AllProtocols = [System.Net.SecurityProtocolType]'Ssl3,Tls,Tls11,Tls12' + [System.Net.ServicePointManager]::SecurityProtocol = $AllProtocols + [System.Net.ServicePointManager]::Expect100Continue = $false + [System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true } + } + + $requestStream = $webRequest.GetRequestStream() + $requestStream.Write($buffer, 0, $buffer.Length) + $requestStream.Flush() + $requestStream.Close() + + $response = $webRequest.getResponse() + if ($response.StatusCode -eq 'OK') { + logmsg ' ... Success' + break + } + else { + logmsg " ... Failed: $($response.StatusCode)" + } + } + catch { + $errorMessage = $_.Exception.Message + logmsg " ... Failed: $errorMessage" + } + + logmsg " ... Report ready failed (${retryNumber}. try out of ${retryCount})" + $retryNumber++ + if ($retryNumber -le $retryCount) { + logmsg " ... sleep for ${retryWaitPeriod} seconds and try again..." + Start-Sleep -Seconds $retryWaitPeriod + } + else { + logmsg " ... All retries failed!" + break + } + } + } +} + +function ejectContextCD($cdrom_drive) { + if (-Not $cdrom_drive) { + return + } + + $eject_cdrom = $context['EJECT_CDROM'] + + if ($null -ne $eject_cdrom -and $eject_cdrom.ToUpper() -eq 'YES') { + logmsg '* Ejecting context CD' + try { + #https://learn.microsoft.com/en-us/windows/win32/api/shldisp/ne-shldisp-shellspecialfolderconstants + $ssfDRIVES = 0x11 + $sh = New-Object -ComObject "Shell.Application" + $sh.Namespace($ssfDRIVES).Items() | Where-Object { $_.Type -eq "CD Drive" -and $_.Path -eq $cdrom_drive.Name } | ForEach-Object { + $_.InvokeVerb("Eject") + logmsg " ... Ejected $($cdrom_drive.Name)" + } + } + catch { + logmsg " ... Failed to eject the CD: $_" + } + } +} + +function removeFile($file) { + if (![string]::IsNullOrEmpty($file) -and (Test-Path $file)) { + logmsg "* Removing the file: ${file}" + Remove-Item -Path $file -Force + } +} + +function removeDir($dir) { + if (![string]::IsNullOrEmpty($dir) -and (Test-Path $dir)) { + logmsg "* Removing the directory: ${dir}" + Remove-Item -Path $dir -Recurse -Force + } +} + +function cleanup($contextPaths) { + if ($contextPaths.contextDrive) { + # Eject CD with 'context.sh' if requested + ejectContextCD $contextPaths.contextDrive + } + else { + # Delete 'context.sh' if not on CD-ROM + removeFile $contextPaths.contextPath + + # and downloaded init scripts + removeDir $contextPaths.contextInitScriptPath + } +} + +function pswrapper($path) { + # source: + # - http://cosmonautdreams.com/2013/09/03/Getting-Powershell-to-run-in-64-bit.html + # - https://ss64.com/nt/syntax-64bit.html + If ($env:PROCESSOR_ARCHITEW6432 -eq "AMD64") { + # This is only set in a x86 Powershell running on a 64bit Windows + + $realpath = [string]$(Resolve-Path "$path") + + # Run 64bit powershell as a subprocess and there execute the command + # + # NOTE: virtual subdir 'sysnative' exists only when running 32bit binary under 64bit system + & "$env:WINDIR\sysnative\windowspowershell\v1.0\powershell.exe" -NonInteractive -NoProfile -Command "$realpath" + } + Else { + & "$path" + } +} + +function authorizeSSHKeyAdmin { + param ( + $authorizedKeys + ) + + $authorizedKeysPath = "$env:ProgramData\ssh\administrators_authorized_keys" + + + + # whitelisting + Set-Content $authorizedKeysPath $authorizedKeys + + if ($?) { + # permissions + icacls.exe $authorizedKeysPath /inheritance:r /grant Administrators:F /grant SYSTEM:F + + logsuccess + } + else { + logfail + } + +} + +function authorizeSSHKeyStandard { + param ( + $authorizedKeys + ) + + $authorizedKeysPath = "$env:USERPROFILE\.ssh" + + New-Item -Force -ItemType Directory -Path $authorizedKeysPath + Set-Content $authorized_keys_path $authorizedKeys + + if ($?) { + logsuccess + } + else { + logfail + } +} + +function authorizeSSHKey { + param ( + $authorizedKeys, + $winadmin + ) + + logmsg "* Authorizing SSH_PUBLIC_KEY: ${authorizedKeys}" + + if ($winadmin -ieq "no") { + authorizeSSHKeyStandard $authorizedKeys + } + else { + authorizeSSHKeyAdmin $authorizedKeys + } + +} + +################################################################################ +# Main +################################################################################ + +# global variable pointing to the private .contextualization directory +$global:ctxDir = "$env:SystemDrive\.onecontext" + +# Check, if above defined context directory exists +If ( !(Test-Path "$ctxDir") ) { + mkdir "$ctxDir" +} + +# Move old logfile away - so we have a current log containing the output of the last boot +If ( Test-Path "$ctxDir\opennebula-context.log" ) { + mv "$ctxDir\opennebula-context.log" "$ctxDir\opennebula-context-old.log" +} +m +# Start now logging to logfile +Start-Transcript -Append -Path "$ctxDir\opennebula-context.log" | Out-Null + +logmsg "* Running Script: $($MyInvocation.MyCommand.Path)" + +Set-ExecutionPolicy unrestricted -Force # not needed if already done once on the VM +[string]$computerName = "$env:computername" +[string]$ConnectionString = "WinNT://$computerName" + +# Check the working WMI +if (-Not (Get-WMIObject -ErrorAction SilentlyContinue Win32_Volume)) { + logmsg "- WMI not ready, exiting" + Stop-Transcript | Out-Null + exit 1 +} + +Write-Host "`r`n" -NoNewline +Write-Host "*********************************`r`n" -NoNewline +Write-Host "*** ENTERING THE SERVICE LOOP ***`r`n" -NoNewline +Write-Host "*********************************`r`n" -NoNewline +Write-Host "`r`n" -NoNewline + +# infinite loop +$checksum = "" +do { + # Stay in this wait-loop until context.sh emerges and its path is stored + $contextPaths = waitForContext($checksum) + + # Parse context file + $context = getContext $contextPaths.contextScriptPath + + # Execute the contextualization actions + extendPartitions $context + setTimeZone $context + addLocalUser $context + enableRemoteDesktop + enablePing + configureNetwork $context + renameComputer $context + runScripts $context $contextPaths + authorizeSSHKey $context["SSH_PUBLIC_KEY"] $context["WINADMIN"] + reportReady $context $contextPaths.contextLetter + + # Save the 'applied' context.sh checksum for the next recontextualization + logmsg "* Calculating the checksum of the file: $($contextPaths.contextScriptPath)" + $checksum = Get-FileHash -Algorithm SHA256 $contextPaths.contextScriptPath + logmsg " ... $($checksum.Hash)" + # and remove the file itself + removeFile $contextPaths.contextScriptPath + + # Cleanup at the end + cleanup $contextPaths + + Write-Host "`r`n" -NoNewline + +} while ($true) + +Stop-Transcript | Out-Null diff --git a/context-windows/unattend.xml b/context-windows/unattend.xml new file mode 100644 index 00000000..8e1c7b7a --- /dev/null +++ b/context-windows/unattend.xml @@ -0,0 +1,118 @@ + + + + + + true + + + + + + en-US + + en-US + en-US + en-US + en-US + + + + + + ClearType + + + true + + 3 + Work + true + + true + true + + + + + + + + + * + + + en-US + en-US + en-us + en-US + en-US + + + + + false + + + 0 + + + + + true + all + @FirewallAPI.dll,-28752 + + + + + UTC + OpenNebulaVM + + + + 0 + + +