diff --git a/doc/07-Repository-Manager.md b/doc/07-Repository-Manager.md new file mode 100644 index 00000000..97574ad1 --- /dev/null +++ b/doc/07-Repository-Manager.md @@ -0,0 +1,17 @@ +# Repository Manager + +Starting with Icinga for Windows v1.6.0, we provide a full feature-set to manage repositories for all components which are developed for Icinga for Windows. This includes all resources like framework, plugins the service binary and the Icinga Agent itself. + +Below you will find a detailed description for core areas of the repository management, explaining on how to work with repositories. + +## Basic Usage + +* [Add existing repositories](repositorymanager/01-Add-Existing-Repositories.md) +* [Sync repositories](repositorymanager/02-Sync-Repositories.md) +* [Enable/Disable/Remove Repositories](repositorymanager/03-Enable-Disable-Remove-Repositories.md) +* [Search Repository for Components](repositorymanager/04-Search-Repository-For-Components.md) +* [Lock/Unlock Components](repositorymanager/05-Lock-Unlock-Components.md) +* [Install Components](repositorymanager/06-Install-Components.md) +* [Create Own Repositories](repositorymanager/07-Create-Own-Repositories.md) + +## Example Configurations diff --git a/doc/31-Changelog.md b/doc/31-Changelog.md index 2eddacbc..32f9bdf5 100644 --- a/doc/31-Changelog.md +++ b/doc/31-Changelog.md @@ -22,6 +22,7 @@ Released closed milestones can be found on [GitHub](https://github.com/Icinga/ic * [#303](https://github.com/Icinga/icinga-powershell-framework/pull/303) Adds support to parse arrays to Icinga Check thresholds functions like `WarnOutOfRange` and adds two new functions `WarnDateTime` and `CritDateTime`, for easier comparing of time stamps. * [#305](https://github.com/Icinga/icinga-powershell-framework/pull/305) Adds a new Cmdlet to test if functions with `Add-Type` are already present inside the current scope of the shell * [#306](https://github.com/Icinga/icinga-powershell-framework/pull/306) Adds new Cmdlet `Exit-IcingaThrowCritical` to throw critical exit with a custom message, either by force or by using string filtering and adds storing of plugin exit codes internally +* [#310](https://github.com/Icinga/icinga-powershell-framework/pull/310) Adds repository management to install components very easily from one or multiple defined source locations * [#314](https://github.com/Icinga/icinga-powershell-framework/pull/314) Adds support to configure on which address TCP sockets are created on, defaults to `loopback` interface * [#316](https://github.com/Icinga/icinga-powershell-framework/pull/316) The reconfigure menu was previously present inside the Icinga Agent sub-menu and is now moved to the main installation menu for the Management Console * [#318](https://github.com/Icinga/icinga-powershell-framework/pull/318) We always enforce the Icinga Framework Code caching now and ship a plain file to build the cache on first loading diff --git a/doc/knowledgebase/IWKB000009.md b/doc/knowledgebase/IWKB000009.md index 34d19f69..2d9e02ef 100644 --- a/doc/knowledgebase/IWKB000009.md +++ b/doc/knowledgebase/IWKB000009.md @@ -32,8 +32,8 @@ if ($null -ne $IcingaService) { return; } - Uninstall-IcingaFrameworkService; - Install-IcingaFrameworkService -Path $IfWPath -User $IfWUser; + Uninstall-IcingaForWindowsService; + Install-IcingaForWindowsService -Path $IfWPath -User $IfWUser; } ``` diff --git a/doc/repositorymanager/01-Add-Existing-Repositories.md b/doc/repositorymanager/01-Add-Existing-Repositories.md new file mode 100644 index 00000000..15d73696 --- /dev/null +++ b/doc/repositorymanager/01-Add-Existing-Repositories.md @@ -0,0 +1,44 @@ +# Add Existing Repositories + +The easiest way to extend the functionality of Icinga for Windows, is by installing new components. Components are delivered by repositories you can add to your environment. This includes external repositories but also internal ones. + +Each repository inherits a file called `ifw.repo.json` on the root level, which contains all informations for available packages, versions and where they can be downloaded from. + +**Note:** Each repository requires to use a unique name on the system. You can add multiple repositories, with different resources and components provided. The name has be unique and has no impact on the installation. It should be a short summary on where the repository is located. + +## Adding Default Repositories + +The best way to demonstrate on how to add new repositories, you can use the default Icinga for Windows repositories. To add an already existing repository, you can use `Add-IcingaRepository` + +### Available Arguments + +| Argument | Type | Description | +| --- |--- | --- | +| Name | String | The unique name of the repository. This name can only exist once on your system | +| RemotePath | String | The path pointing to the location on where the repository is located at. It can either point to the root directory of the folder containing the `ifw.repo.json` or directly to this file. Accepts web, local or network share path. | + +### Icinga for Windows Stable + +The URL pointing to the stable releases is `http://packages.icinga.com/IcingaForWindows/stable`. + +```powershell +Add-IcingaRepository ` + -Name 'Icinga Stable' ` + -RemotePath 'http://packages.icinga.com/IcingaForWindows/stable'; +``` + +### Icinga for Windows Snapshot + +The URL pointing to the snapshot releases is `http://packages.icinga.com/IcingaForWindows/snapshot`. + +```powershell +Add-IcingaRepository ` + -Name 'Icinga Snapshot' ` + -RemotePath 'http://packages.icinga.com/IcingaForWindows/snapshot'; +``` + +## Using Repositories + +Once you added one or more repositories, they will be connected to once you want to install or update your components. The available components and versions will be accessed on runtime, during installation/updates and files downloaded directly when required. + +In case you can only access the internet or some internal repositories on certain times only, you can also [sync existing repositories](02-Sync-Repositories.md). diff --git a/doc/repositorymanager/02-Sync-Repositories.md b/doc/repositorymanager/02-Sync-Repositories.md new file mode 100644 index 00000000..3d8cdb4d --- /dev/null +++ b/doc/repositorymanager/02-Sync-Repositories.md @@ -0,0 +1,142 @@ +# Sync Repositories + +In case you require local copies of repositories to frequently access them or to make a public repository available for systems unable to access public spaces, you can use `Sync-IcingaRepository`. + +Like with [adding existing repositories](01-Add-Existing-Repositories.md), each synced repositories require a unique name you can set with `-Name`. The name will not interfere on how the sync works. + +## Available Arguments + +| Argument | Type | Description | +| --- |--- | --- | +| Name | String | The unique name of the repository. This name can only exist once on your system | +| Path | String | The location on where the files from the remote repository will be synced to. This can either be a local path, a network share or a Linux path, including user and hostname like `icinga@example.com:/vaw/www/icingarepo/` +| RemotePath | String | The path pointing to the location Icinga for Windows tries to lookup all your files. You can either replicate the `Path` variable for network shares for example, or use a web url which is made available based on `Path` to fetch and download files from. If left empty, it will default to the `Path` variable content | +| Source | String | The source from where the repository will be synced from. This can either be pointing directly to the `ifw.repo.json` or the root directory, as long as the file is fetch able from this point. A source can be a web, local or network share | +| UseSCP | Switch | If you set `Path` to a Linux path as mentioned in the first example, you will have to enable this switch to use SCP to copy files from the source to the Linux system. Requires `scp` and `ssh` being installed on the system | +| Force | Switch | This will force the creation of the repository, even if the name of the repository is already assigned. Should be used with caution | +| ForceTrust | Switch | By default repositories are validated with a hash, based on all files present inside the repository. If a repository is not providing a hash, it will be disabled after the sync for security reasons. In case the hash does not match with all files synced afterwards, the repository files will be deleted and the sync aborted. You can use this flag to ignore both states and always add the repository, regardless if the hash matches or the hash is not given | + +## Sync Public Repository + +You can use the sync command to clone an existing public repository for example and fetch all files to a local space. + +### Local Disk Example + +```powershell +Sync-IcingaRepository ` + -Name 'Icinga Stable Local' ` + -Path 'C:\icinga\icinga_stable' ` + -Source 'http://packages.icinga.com/IcingaForWindows/stable'; +``` + +This will sync all files from the public Icinga repository into `C:\icinga\icinga_stable`. As `RemotePath` is not set, it will default to `C:\icinga\icinga_stable` and all files will be fetched from this location. + +### Shared Folder Example + +```powershell +Sync-IcingaRepository ` + -Name 'Icinga Stable Local' ` + -Path 'C:\icinga\icinga_stable' ` + -RemotePath '\\myhost.example.com\icingastable' ` + -Source 'http://packages.icinga.com/IcingaForWindows/stable'; +``` + +We can for example use file sharing, to make certain folders available in our network. This example will download all files into `C:\icinga\icinga_stable`, but tell Icinga for Windows to use your shared network drive `\\myhost.example.com\icingastable` to fetch them from. + +You can now add the repository on different machines with + +```powershell +Add-IcingaRepository ` + -Name 'Icinga Stable Local' ` + -RemotePath '\\myhost.example.com\icingastable'; +``` + +Every file will then be fetched over this network share to the local machines. + +### Linux Webserver Example + +```powershell +Sync-IcingaRepository ` + -Name 'Icinga Stable Internal Web' ` + -Path 'icinga@icingarepo.example.com:/var/www/icingastable/' ` + -RemotePath 'https://icingarepo.example.com/icingastable' ` + -Source 'http://packages.icinga.com/IcingaForWindows/stable' ` + -UseSCP; +``` + +In this example we will sync all files to a Linux machine at `/var/www/icingastable/`. If we are running a local Apache or Nginx, we can create a web resource for this path and make it available in our network. We have to use `-UseSCP`, to tell Icinga for Windows to copy them to the Linux machine over `scp`. Please note that both, `scp` and `ssh` have to be installed on the system. + +On different machines, you can then use + +```powershell +Add-IcingaRepository ` + -Name 'Icinga Stable Internal Web' ` + -RemotePath 'https://icingarepo.example.com/icingastable'; +``` + +to make the repository available. + +### Sync from a Sync Example + +Last but not least we have the method to sync repositories from a sync source, allowing us to distribute all Icinga for Windows files around or network on different zones as example. + +Clone the repository to a local path: + +```powershell +Sync-IcingaRepository ` + -Name 'Icinga Stable Local' ` + -Path 'C:\icinga\icinga_stable' ` + -Source 'http://packages.icinga.com/IcingaForWindows/stable'; +``` + +Clone this repository to our internal web space: + +```powershell +Sync-IcingaRepository ` + -Name 'Icinga Stable Internal Web' ` + -Path 'icinga@icingarepo.example.com:/var/www/icingastable/' ` + -RemotePath 'https://icingarepo.example.com/icingastable' ` + -Source 'C:\icinga\icinga_stable' ` + -UseSCP; +``` + +## Update Sync Repositories + +To update synced repositories, you can use `Update-IcingaRepository`. You can either update a specific repository by using the `-Name` argument or update all by leaving it empty. + +### Available Arguments + +| Argument | Type | Description | +| --- |--- | --- | +| Name | String | The name of the repository to update. Leave empty to update all configured repositories | +| ForceTrust | Switch | By default repositories are validated with a hash, based on all files present inside the repository. If a repository is not providing a hash, it will be disabled after the sync for security reasons. In case the hash does not match with all files synced afterwards, the repository files will be deleted and the sync aborted. You can use this flag to ignore both states and always add the repository, regardless if the hash matches or the hash is not given | + +### Update All Repositories + +Updating all synced repositories is very easy, with a simple command call: + +```powershell +Update-IcingaRepository; +``` + +Once run, all files will be downloaded from the source and validated. In case validation fails or the source is not containing a repository hash, you can use `-ForceTrust` to ignore this and enable it regardless. + +```powershell +Update-IcingaRepository -ForceTrust; +``` + +### Update Specific Repository + +You can update a specific repository, by simply providing it's name: + +```powershell +Update-IcingaRepository -Name 'Icinga Stable Internal Web'; +``` + +Like other methods, `-ForceTrust` will work here as well. + +## Updating Linux Repositories + +There is no special mechanic required, as the entire configuration for the repository is stored inside the Icinga for Windows configuration. + +For easier usage, it is however advised to use SSH keys, as otherwise each sync and update task will require you to enter your SSH password multiple times. diff --git a/doc/repositorymanager/03-Enable-Disable-Remove-Repositories.md b/doc/repositorymanager/03-Enable-Disable-Remove-Repositories.md new file mode 100644 index 00000000..25c4305d --- /dev/null +++ b/doc/repositorymanager/03-Enable-Disable-Remove-Repositories.md @@ -0,0 +1,33 @@ +# Enable/Disable/Remove Repositories + +Besides [adding](01-Add-Existing-Repositories.md) and [syncing])(02-Sync-Repositories.md) of repositories, you can also `enable`, `disable` and `remove` repositories from your local machine. + +Please note that **removing** a repository will only remove the Icinga for Windows configuration and **not** the files on the disk. You have to do this step manually. + +## Enabling Repositories + +In case a repository is disabled, you can enable it with `Enable-IcingaRepository`. + +```powershell +Enable-IcingaRepository -Name 'Icinga Stable Internal Web'; +``` + +## Disabling Repositories + +You can also disable enabled repositories with `Disable-IcingaRepository`. + +```powershell +Disable-IcingaRepository -Name 'Icinga Stable Internal Web'; +``` + +Please note that disabled Icinga repositories will be fully ignored during installation/update tasks and no files from these will be fetched. + +## Removing Repositories + +If you no longer require a certain repository, you can remove it with `Remove-IcingaRepository`. + +```powershell +Remove-IcingaRepository -Name 'Icinga Stable Internal Web'; +``` + +**Note:** This will only remove the repository from the configuration. All possible available files for sync runs or anything related will remain on the disk until you remove them manually. diff --git a/doc/repositorymanager/04-Search-Repository-For-Components.md b/doc/repositorymanager/04-Search-Repository-For-Components.md new file mode 100644 index 00000000..adca3f63 --- /dev/null +++ b/doc/repositorymanager/04-Search-Repository-For-Components.md @@ -0,0 +1,33 @@ +# Search Repository For Components + +Once you [added](01-Add-Existing-Repositories.md) and/or [synced])(02-Sync-Repositories.md) your repositories, you can search them for available components. + +We can do this with the command `Search-IcingaRepository` and filter for a component `Name`, it's `Version` and if we want to include `Release` or `Snapshot` packages. + +## List Everything available + +At the beginning we can have a full search over all repositories and lookup all components made available by our repositories. + +```powershell +Search-IcingaRepository -Name '*' -Release -Snapshot; +``` + +This will print all components including the version, the repository, the source of the repository and the component name. + +**Note:** Disabled repositories are not included inside the search and results. + +## List Certain Release Component + +You can only include certain components for a release branch by using the `Name` and `Release` argument: + +```powershell +Search-IcingaRepository -Name 'agent' -Release; +``` + +## Search For Specific Version + +If you want to check if a certain `Release` version for a component is available inside your repositories, you can specify it with `Version`: + +```powershell +Search-IcingaRepository -Name 'agent' -Version '2.12.5' -Release; +``` diff --git a/doc/repositorymanager/05-Lock-Unlock-Components.md b/doc/repositorymanager/05-Lock-Unlock-Components.md new file mode 100644 index 00000000..be6b9314 --- /dev/null +++ b/doc/repositorymanager/05-Lock-Unlock-Components.md @@ -0,0 +1,38 @@ +# Lock/Unlock Components + +Sometimes you might require to `lock` certain components to a specific version. This means regardless of any version available, this component will not be updated or touched, unless the version it is locked to is not yet installed. + +A lock will override the version suggested by any repository or the user input and always force the version specified. + +If for example you lock the [Icinga Plugins](https://icinga.com/docs/icinga-for-windows/latest/plugins/doc/01-Introduction/) to version 1.6.0, they will not get upgraded to any other version. + +## Locking Commponents + +You can lock any Icinga component with `Lock-IcingaComponent` by simply providing the name of the component and the target version. + +For example we can look our plugins to version 1.6.0: + +```powershell +Lock-IcingaComponent ` + -Name 'plugins' ` + -Version '1.6.0'; +``` + +Now the only version being installed for the plugins is 1.6.0, while all other versions are skipped. You can directly replace the lock for a different version later on, like 1.6.1: + +```powershell +Lock-IcingaComponent ` + -Name 'plugins' ` + -Version '1.6.1'; +``` + +## Unlocking Components + +You can release a lock for a component by using `Unlock-IcingaComponent`. Unlike the locking mechanism, you only require to specify the component name for unlocking it, as each component only accepts one version lock at the time. + +```powershell +Unlock-IcingaComponent ` + -Name 'plugins'; +``` + +Once the lock is removed, updates will be applied again for this component. diff --git a/doc/repositorymanager/06-Install-Components.md b/doc/repositorymanager/06-Install-Components.md new file mode 100644 index 00000000..0ff3a64c --- /dev/null +++ b/doc/repositorymanager/06-Install-Components.md @@ -0,0 +1,60 @@ +# Install Components + +Once you [added](01-Add-Existing-Repositories.md) and/or [synced])(02-Sync-Repositories.md) your repositories and configured - if required - your [locks](05-Lock-Unlock-Components.md), we can start installing components. + +For this we will use `Install-IcingaComponent`. + +**Note:** When the install component is looking up available versions, *all* defined repositories will be searched and the latest version available used by default. If a specific version is set, it will stop once this version is found in one of the repositories and go with this package. + +## Available Arguments + +| Argument | Type | Description | +| --- |--- | --- | +| Name | String | The name of the component you want to install | +| Version | String | Specifies to install a specific version of the component, instead of the latest one found. Is ignored in case you added a [lock](05-Lock-Unlock-Components.md) to this component | +| Release | Switch | Includes release versions only if set. If neither Release nor Snapshot is defined, it will enforce release | +| Snapshot | Switch | Includes snapshots versions only if set. If neither Release nor Snapshot is defined, it will enforce release | +| Confirm | Switch | Skips the message asking you if you want to install this component | +| Force | Switch | In case the same version is already installed, it will be skipped by default. Use this switch to install the same version again | + +## Install Release Components + +You can install release components either by adding the `-Release` switch or by not adding `-Release` and `-Snapshot` at all, to enforce release versions. If no version is specified, the latest version found will be used. Use `-Confirm` in addition to skip the dialog requiring an approval to install it. + +```powershell +Install-IcingaComponent ` + -Name 'agent' ` + -Confirm; +``` + +## Install Specific Component Version + +If your repository contains multiple versions, you can specify which version will be installed: + +```powershell +Install-IcingaComponent ` + -Name 'agent' ` + -Version '2.11.2' ` + -Confirm; +``` + +## Reinstall Installed Component + +If a component with a specific version is already installed, you can use `-Force` to re-install it. + +```powershell +Install-IcingaComponent ` + -Name 'agent' ` + -Version '2.11.5' ` + -Confirm ` + -Force; +``` + +This also works for auto detected latest versions: + +```powershell +Install-IcingaComponent ` + -Name 'agent' ` + -Confirm ` + -Force; +``` diff --git a/doc/repositorymanager/07-Create-Own-Repositories.md b/doc/repositorymanager/07-Create-Own-Repositories.md new file mode 100644 index 00000000..7ae504bc --- /dev/null +++ b/doc/repositorymanager/07-Create-Own-Repositories.md @@ -0,0 +1,50 @@ +# Create Own Repositories + +Besides [adding](01-Add-Existing-Repositories.md) and/or [syncing])(02-Sync-Repositories.md) already existing repositories, you can create entire new ones from scratch with `New-IcingaRepository`. + +## Preparing Repositories + +To prepare your new repository, you will simply require an `empty` folder somewhere on your local Windows machine or accessible network share. For example we can create a new folder directly under `C:`, like `C:\icinga_repositories\custom`. + +Now after having an `empty` folder, copy all files you want to add to this repository there. This includes the `.zip` files for Icinga for Windows components, the Icinga Agents `.msi` files and the Icinga for Windows `Service` `.zip` files which include the `.exe` and the `.md5` hash file. + +## Initialize The Repository + +Now as the folder is prepared and all files are placed inside, we can run `New-IcingaRepository` with our configuration. + +### Available Arguments + +| Argument | Type | Description | +| --- |--- | --- | +| Name | String | The name of the repository to add. Only for local references required. | +| Path | String | The location of the path you prepared in the first step in which all files are inside, for creating your repository | +| RemotePath | String | Will add the remote path for later adding of this repository to another system. Only suitable for network shares and is optional | +| Force | Switch | In case a repository with the given name is already present, it will be overwritten | + +### Create Our Repository + +As we now understand on which arguments are available, we can now use `New-IcingaRepository` to create your repository: + +```powershell +New-IcingaRepository ` + -Name 'My Local Repo' ` + -Path 'C:\icinga_repositories\custom'; +``` + +Depending on the amount of files inside, every single one will be analyzed for their component name and version and a new repo file `ifw.repo.json` is created. From the local Windows machine, you can also use this repository to install components from, as it will be added automatically. + +### Sync Own Repositories + +Now as we created our own repository with own files we checked, we can use the `Sync-IcingaRepository` Cmdlet to either sync from this machine to another location or ir vice-versa, depending on your configuration. +You can read more about this on the [sync repositories](02-Sync-Repositories.md) section. + +#### Example + +```powershell +Sync-IcingaRepository ` + -Name 'My Local Repo Sync' ` + -Path 'icinga@icingarepo.example.com:/var/www/icingacustom/' ` + -RemotePath 'https://icingarepo.example.com/icingacustom' ` + -Source 'C:\icinga_repositories\custom' ` + -UseSCP; +``` diff --git a/lib/core/framework/Install-IcingaFrameworkService.psm1 b/lib/core/framework/Install-IcingaForWindowsService.psm1 similarity index 89% rename from lib/core/framework/Install-IcingaFrameworkService.psm1 rename to lib/core/framework/Install-IcingaForWindowsService.psm1 index a8d31732..e3bd4013 100644 --- a/lib/core/framework/Install-IcingaFrameworkService.psm1 +++ b/lib/core/framework/Install-IcingaForWindowsService.psm1 @@ -7,9 +7,9 @@ .FUNCTIONALITY Installs the Icinga PowerShell Services as a Windows service .EXAMPLE - PS>Install-IcingaFrameworkService -Path C:\Program Files\icinga-service\icinga-service.exe; + PS>Install-IcingaForWindowsService -Path C:\Program Files\icinga-service\icinga-service.exe; .EXAMPLE - PS>Install-IcingaFrameworkService -Path C:\Program Files\icinga-service\icinga-service.exe -User 'NT Authority\NetworkService'; + PS>Install-IcingaForWindowsService -Path C:\Program Files\icinga-service\icinga-service.exe -User 'NT Authority\NetworkService'; .PARAMETER Path The location on where the service binary executable is found .PARAMETER User @@ -24,7 +24,7 @@ https://github.com/Icinga/icinga-powershell-framework #> -function Install-IcingaFrameworkService() +function Install-IcingaForWindowsService() { param( $Path, @@ -93,3 +93,5 @@ function Install-IcingaFrameworkService() return (Set-IcingaAgentServiceUser -User $User -Password $Password -Service 'icingapowershell'); } + +Set-Alias -Name 'Install-IcingaFrameworkService' -Value 'Install-IcingaForWindowsService'; diff --git a/lib/core/framework/Uninstall-IcingaForWindows.psm1 b/lib/core/framework/Uninstall-IcingaForWindows.psm1 index bf544fcc..3e59e379 100644 --- a/lib/core/framework/Uninstall-IcingaForWindows.psm1 +++ b/lib/core/framework/Uninstall-IcingaForWindows.psm1 @@ -48,7 +48,7 @@ function Uninstall-IcingaForWindows() Write-IcingaConsoleNotice 'Uninstalling Icinga Agent'; Uninstall-IcingaAgent -RemoveDataFolder | Out-Null; Write-IcingaConsoleNotice 'Uninstalling Icinga for Windows service'; - Uninstall-IcingaFrameworkService | Out-Null; + Uninstall-IcingaForWindowsService | Out-Null; $HasErrors = $FALSE; diff --git a/lib/core/framework/Uninstall-IcingaForWindowsService.psm1 b/lib/core/framework/Uninstall-IcingaForWindowsService.psm1 new file mode 100644 index 00000000..d1a94127 --- /dev/null +++ b/lib/core/framework/Uninstall-IcingaForWindowsService.psm1 @@ -0,0 +1,73 @@ +<# +.SYNOPSIS + Uninstalls the Icinga PowerShell Service as a Windows Service +.DESCRIPTION + Uninstalls the Icinga PowerShell Service as a Windows Service. The service binary + will be left on the system. +.FUNCTIONALITY + Uninstalls the Icinga PowerShell Service as a Windows Service +.EXAMPLE + PS>Uninstall-IcingaForWindowsService; +.INPUTS + System.String +.OUTPUTS + System.Object +.LINK + https://github.com/Icinga/icinga-powershell-framework +#> + +function Uninstall-IcingaForWindowsService() +{ + param ( + [switch]$RemoveFiles = $FALSE + ); + + $ServiceData = Get-IcingaForWindowsServiceData; + + Stop-IcingaService 'icingapowershell'; + Start-Sleep -Seconds 1; + + $ServiceCreation = Start-IcingaProcess -Executable 'sc.exe' -Arguments 'delete icingapowershell'; + + switch ($ServiceCreation.ExitCode) { + 0 { + Write-IcingaConsoleNotice 'Icinga PowerShell Service was successfully removed'; + } + 1060 { + Write-IcingaConsoleWarning 'The Icinga PowerShell Service is not installed'; + } + Default { + throw ([string]::Format('Failed to install Icinga PowerShell Service: {0}{1}', $ServiceCreation.Message, $ServiceCreation.Error)); + } + } + + if ($RemoveFiles -eq $FALSE) { + return $TRUE; + } + + if ([string]::IsNullOrEmpty($ServiceData.Directory) -Or (Test-Path $ServiceData.Directory) -eq $FALSE) { + return $TRUE; + } + + $ServiceFolderContent = Get-ChildItem -Path $ServiceData.Directory; + + foreach ($entry in $ServiceFolderContent) { + if ($entry.Name -eq 'icinga-service.exe' -Or $entry.Name -eq 'icinga-service.exe.md5' -Or $entry.Name -eq 'icinga-service.exe.update') { + Remove-Item $entry.FullName -Force; + Write-IcingaConsoleNotice 'Removing file "{0}"' -Objects $entry.FullName; + } + } + + $ServiceFolderContent = Get-ChildItem -Path $ServiceData.Directory; + + if ($ServiceFolderContent.Count -eq 0) { + Remove-Item $ServiceData.Directory; + Write-IcingaConsoleNotice 'Removing directory "{0}"' -Objects $ServiceData.Directory; + } else { + Write-IcingaConsoleWarning 'Unable to remove folder "{0}", because there are still files inside.' -Objects $ServiceData.Directory; + } + + return $TRUE; +} + +Set-Alias -Name 'Uninstall-IcingaFrameworkService' -Value 'Uninstall-IcingaForWindowsService'; diff --git a/lib/core/framework/Uninstall-IcingaFrameworkService.psm1 b/lib/core/framework/Uninstall-IcingaFrameworkService.psm1 deleted file mode 100644 index 1d13f10e..00000000 --- a/lib/core/framework/Uninstall-IcingaFrameworkService.psm1 +++ /dev/null @@ -1,37 +0,0 @@ -<# -.SYNOPSIS - Uninstalls the Icinga PowerShell Service as a Windows Service -.DESCRIPTION - Uninstalls the Icinga PowerShell Service as a Windows Service. The service binary - will be left on the system. -.FUNCTIONALITY - Uninstalls the Icinga PowerShell Service as a Windows Service -.EXAMPLE - PS>Uninstall-IcingaFrameworkService; -.INPUTS - System.String -.OUTPUTS - System.Object -.LINK - https://github.com/Icinga/icinga-powershell-framework -#> - -function Uninstall-IcingaFrameworkService() -{ - Stop-IcingaService 'icingapowershell'; - Start-Sleep -Seconds 1; - - $ServiceCreation = Start-IcingaProcess -Executable 'sc.exe' -Arguments 'delete icingapowershell'; - - switch ($ServiceCreation.ExitCode) { - 0 { - Write-IcingaConsoleNotice 'Icinga PowerShell Service was successfully removed'; - } - 1060 { - Write-IcingaConsoleWarning 'The Icinga PowerShell Service is not installed'; - } - Default { - throw ([string]::Format('Failed to install Icinga PowerShell Service: {0}{1}', $ServiceCreation.Message, $ServiceCreation.Error)); - } - } -} diff --git a/lib/core/icingaagent/getters/Get-IcingaAgentInstallation.psm1 b/lib/core/icingaagent/getters/Get-IcingaAgentInstallation.psm1 index 375d0efd..00be24b0 100644 --- a/lib/core/icingaagent/getters/Get-IcingaAgentInstallation.psm1 +++ b/lib/core/icingaagent/getters/Get-IcingaAgentInstallation.psm1 @@ -18,6 +18,13 @@ function Get-IcingaAgentInstallation() } } + $IcingaService = Get-IcingaServices -Service 'icinga2'; + $ServiceUser = 'NT AUTHORITY\NetworkService'; + + if ($null -ne $IcingaService) { + $ServiceUser = $IcingaService.icinga2.configuration.ServiceUser; + } + if ($null -eq $IcingaData) { return @{ 'Installed' = $FALSE; @@ -26,6 +33,7 @@ function Get-IcingaAgentInstallation() 'Architecture' = $architecture; 'Uninstaller' = ''; 'InstallDate' = ''; + 'User' = $ServiceUser; }; } @@ -36,5 +44,6 @@ function Get-IcingaAgentInstallation() 'Architecture' = $architecture; 'Uninstaller' = $IcingaData.UninstallString.Replace("MsiExec.exe ", ""); 'InstallDate' = $IcingaData.InstallDate; + 'User' = $ServiceUser; }; } diff --git a/lib/core/icingaagent/misc/Start-IcingaAgentInstallWizard.psm1 b/lib/core/icingaagent/misc/Start-IcingaAgentInstallWizard.psm1 index 867ddd5d..da7ce4c9 100644 --- a/lib/core/icingaagent/misc/Start-IcingaAgentInstallWizard.psm1 +++ b/lib/core/icingaagent/misc/Start-IcingaAgentInstallWizard.psm1 @@ -724,7 +724,7 @@ function Start-IcingaAgentInstallWizard() Set-IcingaAgentNodeName -Hostname $Hostname; Set-IcingaAgentServiceUser -User $ServiceUser -Password $ServicePass -SetPermission | Out-Null; if ($InstallFrameworkService) { - Install-IcingaFrameworkService -Path $ServiceBin -User $ServiceUser -Password $ServicePass | Out-Null; + Install-IcingaForWindowsService -Path $ServiceBin -User $ServiceUser -Password $ServicePass | Out-Null; } Register-IcingaBackgroundDaemon -Command 'Start-IcingaServiceCheckDaemon'; Install-IcingaAgentBaseFeatures; diff --git a/lib/core/icingaagent/setters/Set-IcingaAgentServiceUser.psm1 b/lib/core/icingaagent/setters/Set-IcingaAgentServiceUser.psm1 index db206a98..f8feae7a 100644 --- a/lib/core/icingaagent/setters/Set-IcingaAgentServiceUser.psm1 +++ b/lib/core/icingaagent/setters/Set-IcingaAgentServiceUser.psm1 @@ -1,6 +1,6 @@ function Set-IcingaAgentServiceUser() { - param( + param ( [string]$User, [securestring]$Password, [string]$Service = 'icinga2', diff --git a/lib/core/installer/Install-Icinga.psm1 b/lib/core/installer/Install-Icinga.psm1 index c74caa2b..00919d46 100644 --- a/lib/core/installer/Install-Icinga.psm1 +++ b/lib/core/installer/Install-Icinga.psm1 @@ -103,10 +103,14 @@ function Install-Icinga() 'Help' = 'Allows you to install Icinga for Windows with all required components and options.' }, @{ - 'Caption' = 'Update environment'; - 'Command' = ''; - 'Help' = 'Allows you to modify your current Icinga for Windows installation.'; - 'Disabled' = $TRUE; + 'Caption' = 'Install Components'; + 'Command' = 'Show-IcingaForWindowsMenuInstallComponents'; + 'Help' = 'Allows you to install new components for Icinga for Windows from your repositories.'; + }, + @{ + 'Caption' = 'Update environment'; + 'Command' = 'Show-IcingaForWindowsMenuUpdateComponents'; + 'Help' = 'Allows you to modify your current Icinga for Windows installation.'; }, @{ 'Caption' = 'Manage environment'; diff --git a/lib/core/installer/Start-IcingaForWindowsInstallation.psm1 b/lib/core/installer/Start-IcingaForWindowsInstallation.psm1 index 22bb8262..ab3faf60 100644 --- a/lib/core/installer/Start-IcingaForWindowsInstallation.psm1 +++ b/lib/core/installer/Start-IcingaForWindowsInstallation.psm1 @@ -21,18 +21,17 @@ function Start-IcingaForWindowsInstallation() # Icinga Agent $AgentVersion = Get-IcingaForWindowsInstallerValuesFromStep -InstallerStep 'Show-IcingaForWindowsInstallationMenuEnterIcingaAgentVersion'; - $AgentPackageType = Get-IcingaForWindowsInstallerStepSelection -InstallerStep 'Show-IcingaForWindowsInstallerMenuSelectIcingaAgentSource'; + $InstallIcingaAgent = Get-IcingaForWindowsInstallerStepSelection -InstallerStep 'Show-IcingaForWindowsInstallerMenuSelectInstallIcingaAgent'; $AgentInstallDir = Get-IcingaForWindowsInstallerValuesFromStep -InstallerStep 'Show-IcingaForWindowsInstallationMenuEnterIcingaAgentDirectory'; $ServiceUser = Get-IcingaForWindowsInstallerValuesFromStep -InstallerStep 'Show-IcingaForWindowsInstallationMenuEnterIcingaAgentUser'; $ServicePassword = Get-IcingaForWindowsInstallerValuesFromStep -InstallerStep 'Show-IcingaForWindowsInstallationMenuEnterIcingaAgentServicePassword'; # Icinga for Windows Service - $WindowsServiceType = Get-IcingaForWindowsInstallerStepSelection -InstallerStep 'Show-IcingaForWindowsInstallerMenuSelectWindowsServiceSource'; + $InstallPSService = Get-IcingaForWindowsInstallerStepSelection -InstallerStep 'Show-IcingaForWindowsInstallerMenuSelectInstallIcingaForWindowsService'; $WindowsServiceDir = Get-IcingaForWindowsInstallerValuesFromStep -InstallerStep 'Show-IcingaForWindowsInstallationMenuEnterWindowsServiceDirectory'; # Icinga for Windows Plugins - $WindowsPluginsType = Get-IcingaForWindowsInstallerStepSelection -InstallerStep 'Show-IcingaForWindowsInstallerMenuSelectIcingaPluginsSource'; - $WindowsPluginsPackage = Get-IcingaForWindowsInstallerValuesFromStep -InstallerStep 'Show-IcingaForWindowsInstallerMenuEnterPluginsPackageSource'; + $InstallPluginChoice = Get-IcingaForWindowsInstallerStepSelection -InstallerStep 'Show-IcingaForWindowsInstallerMenuSelectInstallIcingaPlugins'; # Global Zones $GlobalZonesType = Get-IcingaForWindowsInstallerStepSelection -InstallerStep 'Show-IcingaForWindowsInstallerMenuSelectGlobalZones'; @@ -43,10 +42,15 @@ function Start-IcingaForWindowsInstallation() $IcingaEndpoints = Get-IcingaForWindowsInstallerValuesFromStep -InstallerStep 'Show-IcingaForWindowsInstallerMenuEnterIcingaParentNodes'; $IcingaPort = Get-IcingaForWindowsInstallerValuesFromStep -InstallerStep 'Show-IcingaForWindowsInstallationMenuEnterIcingaPort'; + # Repository + $IcingaStableRepo = Get-IcingaForWindowsInstallerValuesFromStep -InstallerStep 'Show-IcingaForWindowsInstallationMenuStableRepository'; + + # JEA Profile + $InstallJEAProfile = Get-IcingaForWindowsInstallerStepSelection -InstallerStep 'Show-IcingaForWindowsInstallerMenuSelectInstallJEAProfile'; + $Hostname = ''; $GlobalZones = @(); $IcingaParentAddresses = @(); - $AgentPackageSource = '' $ServicePackageSource = '' $ServiceSourceGitHub = $FALSE; $InstallAgent = $TRUE; @@ -55,6 +59,10 @@ function Start-IcingaForWindowsInstallation() $PluginPackageRelease = $FALSE; $PluginPackageSnapshot = $FALSE; + if ([string]::IsNullOrEmpty($IcingaStableRepo) -eq $FALSE) { + Add-IcingaRepository -Name 'Icinga Stable' -RemotePath $IcingaStableRepo; + } + foreach ($endpoint in $IcingaEndpoints) { $EndpointAddress = Get-IcingaForWindowsInstallerValuesFromStep -InstallerStep 'Show-IcingaForWindowsInstallerMenuEnterIcingaParentAddresses' -Parent $endpoint; @@ -114,58 +122,39 @@ function Start-IcingaForWindowsInstallation() } } - switch ($AgentPackageType) { + switch ($InstallIcingaAgent) { '0' { # Install Icinga Agent from packages.icinga.com - $AgentPackageSource = ' https://packages.icinga.com/windows'; + $InstallAgent = $TRUE; break; }; '1' { - # Install Icinga Agent from custom source - $AgentPackageSource = (Get-IcingaForWindowsInstallerValuesFromStep -InstallerStep 'Show-IcingaForWindowsInstallerMenuEnterIcingaAgentPackageSource'); - break; - }; - '2' { # Do not install Icinga Agent $InstallAgent = $FALSE; break; } } - switch ($WindowsServiceType) { + switch ($InstallPSService) { '0' { - #GitHub - $ServiceSourceGitHub = $TRUE; + # Install Icinga for Windows Service + $InstallService = $TRUE; break; }; '1' { - # Custom location - $ServicePackageSource = (Get-IcingaForWindowsInstallerValuesFromStep -InstallerStep 'Show-IcingaForWindowsInstallerMenuEnterWindowsServicePackageSource'); - break; - }; - '2' { # Do not install Icinga for Windows service $InstallService = $FALSE; break; } } - switch ($WindowsPluginsType) { + switch ($InstallPluginChoice) { '0' { - # Download Release from GitHub + # Download stable release $PluginPackageRelease = $TRUE; break; }; '1' { - # Download Snapshot (master) from GitHub - $PluginPackageSnapshot = $TRUE; - break; - }; - '2' { - # Use custom package source - break; - }; - '3' { # Do not install plugins $InstallPlugins = $FALSE; break; @@ -173,7 +162,8 @@ function Start-IcingaForWindowsInstallation() } if ($InstallAgent) { - Install-IcingaAgent -Version $AgentVersion -Source $AgentPackageSource -InstallDir $AgentInstallDir -AllowUpdates $TRUE | Out-Null; + Set-IcingaPowerShellConfig -Path 'Framework.Icinga.AgentLocation' -Value $AgentInstallDir; + Install-IcingaComponent -Name 'agent' -Version $AgentVersion -Confirm -Release; Reset-IcingaAgentConfigFile; Move-IcingaAgentDefaultConfig; Set-IcingaAgentNodeName -Hostname $Hostname; @@ -198,14 +188,16 @@ function Start-IcingaForWindowsInstallation() Write-IcingaAgentZonesConfig -Endpoints $IcingaEndpoints -EndpointConnections $IcingaParentAddresses -ParentZone $IcingaZone -GlobalZones $GlobalZones -Hostname $Hostname; if ($InstallService) { - $ServiceData = Get-IcingaFrameworkServiceBinary -FrameworkServiceUrl $ServicePackageSource -Release:$ServiceSourceGitHub -ServiceDirectory $WindowsServiceDir; + Set-IcingaPowerShellConfig -Path 'Framework.Icinga.IcingaForWindowsService' -Value $WindowsServiceDir; + Set-IcingaPowerShellConfig -Path 'Framework.Icinga.ServiceUser' -User $ServiceUser; + Set-IcingaInternalPowerShellServicePassword -Password (ConvertTo-IcingaSecureString $ServicePassword); - Install-IcingaFrameworkService -Path $ServiceData.ServiceBin -User $ServiceUser -Password (ConvertTo-IcingaSecureString $ServicePassword) | Out-Null; + Install-IcingaComponent -Name 'service' -Release -Confirm; Register-IcingaBackgroundDaemon -Command 'Start-IcingaServiceCheckDaemon'; } if ($InstallPlugins) { - Install-IcingaFrameworkComponent -Name 'plugins' -Release:$PluginPackageRelease -Snapshot:$PluginPackageSnapshot -Url $WindowsPluginsPackage | Out-Null; + Install-IcingaComponent -Name 'plugins' -Release:$PluginPackageRelease -Snapshot:$PluginPackageSnapshot -Confirm; } switch ($FirewallType) { @@ -232,6 +224,20 @@ function Start-IcingaForWindowsInstallation() Restart-IcingaService 'icingapowershell'; } + switch ($InstallJEAProfile) { + '0' { + Install-IcingaJEAProfile; + break; + }; + '1' { + Install-IcingaSecurity; + break; + }; + '2' { + # Do not install JEA profile + } + } + # Update configuration and clear swap $ConfigSwap = Get-IcingaPowerShellConfig -Path 'Framework.Config.Swap'; Set-IcingaPowerShellConfig -Path 'Framework.Config.Swap' -Value $null; diff --git a/lib/core/installer/menu/installation/AdvancedEntries.psm1 b/lib/core/installer/menu/installation/AdvancedEntries.psm1 index 943d0b2c..c6840483 100644 --- a/lib/core/installer/menu/installation/AdvancedEntries.psm1 +++ b/lib/core/installer/menu/installation/AdvancedEntries.psm1 @@ -15,12 +15,14 @@ function Add-IcingaForWindowsInstallationAdvancedEntries() Show-IcingaForWindowsInstallerMenuSelectGlobalZones -Automated -Advanced; Show-IcingaForWindowsInstallationMenuEnterCustomGlobalZones -Automated -Advanced; Show-IcingaForWindowsInstallationMenuEnterIcingaAgentVersion -Automated -Advanced; - Show-IcingaForWindowsInstallerMenuSelectIcingaAgentSource -Automated -Advanced; + Show-IcingaForWindowsInstallerMenuSelectInstallIcingaAgent -Automated -Advanced; Show-IcingaForWindowsInstallationMenuEnterIcingaAgentDirectory -Automated -Advanced; Show-IcingaForWindowsInstallationMenuEnterIcingaAgentUser -Automated -Advanced; - Show-IcingaForWindowsInstallerMenuSelectIcingaPluginsSource -Automated -Advanced; - Show-IcingaForWindowsInstallerMenuSelectWindowsServiceSource -Automated -Advanced; + Show-IcingaForWindowsInstallerMenuSelectInstallIcingaPlugins -Automated -Advanced; + Show-IcingaForWindowsInstallerMenuSelectInstallIcingaForWindowsService -Automated -Advanced; Show-IcingaForWindowsInstallationMenuEnterWindowsServiceDirectory -Automated -Advanced; + Show-IcingaForWindowsInstallationMenuStableRepository -Automated -Advanced; + Show-IcingaForWindowsInstallerMenuSelectInstallJEAProfile -Automated -Advanced; Enable-IcingaFrameworkConsoleOutput; diff --git a/lib/core/installer/menu/installation/agent/EnterIcingaAgentPackageSource.psm1 b/lib/core/installer/menu/installation/agent/EnterIcingaAgentPackageSource.psm1 deleted file mode 100644 index fef2184d..00000000 --- a/lib/core/installer/menu/installation/agent/EnterIcingaAgentPackageSource.psm1 +++ /dev/null @@ -1,31 +0,0 @@ -function Show-IcingaForWindowsInstallerMenuEnterIcingaAgentPackageSource() -{ - param ( - [array]$Value = @( 'https://packages.icinga.com/windows/' ), - [string]$DefaultInput = 'c', - [switch]$JumpToSummary = $FALSE, - [switch]$Automated = $FALSE, - [switch]$Advanced = $FALSE - ); - - Show-IcingaForWindowsInstallerMenu ` - -Header 'Please enter the location on where to find your Icinga Agent installation package:' ` - -Entries @( - @{ - 'Command' = 'Show-IcingaForWindowsInstallerConfigurationSummary'; - 'Help' = 'The location on where to find your Icinga Agent .MSI packages. You can specify a local path "C:\icinga\msi", a network path "\\example.com\software\icinga\" or a web path "https://example.com/icinga/windows". For the last variant it is required that the web server is printing the directory file list.'; - } - ) ` - -DefaultIndex $DefaultInput ` - -AddConfig ` - -ConfigLimit 1 ` - -DefaultValues $Value ` - -ContinueFirstValue ` - -MandatoryValue ` - -JumpToSummary:$JumpToSummary ` - -ConfigElement ` - -Automated:$Automated ` - -Advanced:$Advanced; -} - -Set-Alias -Name 'IfW-AgentPackageSource' -Value 'Show-IcingaForWindowsInstallerMenuEnterIcingaAgentPackageSource'; diff --git a/lib/core/installer/menu/installation/agent/InstallAgent.psm1 b/lib/core/installer/menu/installation/agent/InstallAgent.psm1 new file mode 100644 index 00000000..b9d4fef2 --- /dev/null +++ b/lib/core/installer/menu/installation/agent/InstallAgent.psm1 @@ -0,0 +1,32 @@ +function Show-IcingaForWindowsInstallerMenuSelectInstallIcingaAgent() +{ + param ( + [array]$Value = @(), + [string]$DefaultInput = '0', + [switch]$JumpToSummary = $FALSE, + [switch]$Automated = $FALSE, + [switch]$Advanced = $FALSE + ); + + Show-IcingaForWindowsInstallerMenu ` + -Header 'Please select if the Icinga Agent should be installed' ` + -Entries @( + @{ + 'Caption' = 'Install Icinga Agent'; + 'Command' = 'Show-IcingaForWindowsInstallerConfigurationSummary'; + 'Help' = 'Downloads the Icinga Agent from the specified stable repository and installs it'; + }, + @{ + 'Caption' = 'Do not install Icinga Agent'; + 'Command' = 'Show-IcingaForWindowsInstallerConfigurationSummary'; + 'Help' = 'Do not install the Icinga Agent on this system'; + } + ) ` + -DefaultIndex $DefaultInput ` + -JumpToSummary:$FALSE ` + -ConfigElement ` + -Automated:$Automated ` + -Advanced:$Advanced; +} + +Set-Alias -Name 'IfW-InstallAgent' -Value 'Show-IcingaForWindowsInstallerMenuSelectInstallIcingaAgent'; diff --git a/lib/core/installer/menu/installation/agent/SelectIcingaAgentSource.psm1 b/lib/core/installer/menu/installation/agent/SelectIcingaAgentSource.psm1 deleted file mode 100644 index 8d53f443..00000000 --- a/lib/core/installer/menu/installation/agent/SelectIcingaAgentSource.psm1 +++ /dev/null @@ -1,42 +0,0 @@ -function Show-IcingaForWindowsInstallerMenuSelectIcingaAgentSource() -{ - param ( - [array]$Value = @(), - [string]$DefaultInput = '0', - [switch]$JumpToSummary = $FALSE, - [switch]$Automated = $FALSE, - [switch]$Advanced = $FALSE - ); - - Show-IcingaForWindowsInstallerMenu ` - -Header 'Please select where your Icinga Agent .MSI package is downloaded from:' ` - -Entries @( - @{ - 'Caption' = 'Download from "https://packages.icinga.com"'; - 'Command' = 'Show-IcingaForWindowsInstallerConfigurationSummary'; - 'Help' = 'Download the Icinga Agent directly from "https://packages.icinga.com" for the specified version'; - }, - @{ - 'Caption' = 'Use custom source'; - 'Command' = 'Show-IcingaForWindowsInstallerMenuEnterIcingaAgentPackageSource'; - 'Help' = 'Specify the path on where the .MSI installer packages for the Icinga Agent can be found in your environment'; - }, - @{ - 'Caption' = 'Do not install Icinga Agent'; - 'Command' = 'Show-IcingaForWindowsInstallerConfigurationSummary'; - 'Help' = 'Do not install the Icinga Agent on this system'; - } - ) ` - -DefaultIndex $DefaultInput ` - -JumpToSummary:$FALSE ` - -ConfigElement ` - -Automated:$Automated ` - -Advanced:$Advanced; - - # In case we use the default location, delete our custom location entry - if (Get-IcingaForWindowsManagementConsoleLastInput -ne '1') { - Remove-IcingaForWindowsInstallerConfigEntry -Menu 'Show-IcingaForWindowsInstallerMenuEnterIcingaAgentPackageSource'; - } -} - -Set-Alias -Name 'IfW-AgentSource' -Value 'Show-IcingaForWindowsInstallerMenuSelectIcingaAgentSource'; diff --git a/lib/core/installer/menu/installation/director/DirectorTemplate.psm1 b/lib/core/installer/menu/installation/director/DirectorTemplate.psm1 index 0c712d9a..8ddd0211 100644 --- a/lib/core/installer/menu/installation/director/DirectorTemplate.psm1 +++ b/lib/core/installer/menu/installation/director/DirectorTemplate.psm1 @@ -155,15 +155,14 @@ function Resolve-IcingaForWindowsManagementConsoleInstallationDirectorTemplate() if ($DirectorConfig.install_framework_service -eq 0) { # Do not install - $InstallServiceSelection = 2; + $InstallServiceSelection = 1; } else { - # TODO: This is currently not supported. We use the "default" config for installing from GitHub by now $InstallServiceSelection = 0; } if ($DirectorConfig.install_framework_plugins -eq 0) { # Do not install - $InstallPluginsSelection = 3; + $InstallPluginsSelection = 1; } else { # TODO: This is currently not supported. We use the "default" config for installing from GitHub by now $InstallPluginsSelection = 0; @@ -174,8 +173,8 @@ function Resolve-IcingaForWindowsManagementConsoleInstallationDirectorTemplate() Add-IcingaForWindowsInstallationAdvancedEntries; Disable-IcingaFrameworkConsoleOutput; - Show-IcingaForWindowsInstallerMenuSelectIcingaPluginsSource -DefaultInput $InstallPluginsSelection -Value @() -Automated; - Show-IcingaForWindowsInstallerMenuSelectWindowsServiceSource -DefaultInput $InstallServiceSelection -Value @() -Automated; + Show-IcingaForWindowsInstallerMenuSelectInstallIcingaPlugins -DefaultInput $InstallPluginsSelection -Value @() -Automated; + Show-IcingaForWindowsInstallerMenuSelectInstallIcingaForWindowsService -DefaultInput $InstallServiceSelection -Value @() -Automated; Show-IcingaForWindowsInstallerMenuSelectOpenWindowsFirewall -DefaultInput $WindowsFirewallSelection -Value @() -Automated; if ($Register) { diff --git a/lib/core/installer/menu/installation/framework/IcingaRepository.psm1 b/lib/core/installer/menu/installation/framework/IcingaRepository.psm1 new file mode 100644 index 00000000..c7f7e4e1 --- /dev/null +++ b/lib/core/installer/menu/installation/framework/IcingaRepository.psm1 @@ -0,0 +1,30 @@ +function Show-IcingaForWindowsInstallationMenuStableRepository() +{ + param ( + [array]$Value = @( 'https://packages.icinga.com/IcingaForWindows/stable' ), + [string]$DefaultInput = 'c', + [switch]$JumpToSummary = $FALSE, + [switch]$Automated = $FALSE, + [switch]$Advanced = $FALSE + ); + + Show-IcingaForWindowsInstallerMenu ` + -Header 'Please enter the path or Url for your stable Icinga repository:' ` + -Entries @( + @{ + 'Command' = 'Show-IcingaForWindowsInstallerConfigurationSummary'; + 'Help' = 'This is the stable repository from where all packages of Icinga for Windows are downloaded and intstalled from. Defaults to "https://packages.icinga.com/IcingaForWindows/stable"'; + } + ) ` + -DefaultIndex $DefaultInput ` + -AddConfig ` + -ConfigLimit 1 ` + -DefaultValues @( $Value ) ` + -MandatoryValue ` + -JumpToSummary:$JumpToSummary ` + -ConfigElement ` + -Automated:$Automated ` + -Advanced:$Advanced; +} + +Set-Alias -Name 'IfW-StableRepository' -Value 'Show-IcingaForWindowsInstallationMenuStableRepository'; diff --git a/lib/core/installer/menu/installation/framework/InstallJEA.psm1 b/lib/core/installer/menu/installation/framework/InstallJEA.psm1 new file mode 100644 index 00000000..3713575b --- /dev/null +++ b/lib/core/installer/menu/installation/framework/InstallJEA.psm1 @@ -0,0 +1,41 @@ +function Show-IcingaForWindowsInstallerMenuSelectInstallJEAProfile() +{ + param ( + [array]$Value = @(), + [string]$DefaultInput = '2', + [switch]$JumpToSummary = $FALSE, + [switch]$Automated = $FALSE, + [switch]$Advanced = $FALSE + ); + + if ($PSVersionTable.PSVersion -lt '5.0.0.0') { + return; + } + + Show-IcingaForWindowsInstallerMenu ` + -Header 'Please select if you want to install the JEA profile for the assigned service user or to create a managed user' ` + -Entries @( + @{ + 'Caption' = 'Install JEA Profile'; + 'Command' = 'Show-IcingaForWindowsInstallerConfigurationSummary'; + 'Help' = 'Installs the Icinga for Windows JEA profile for the specified service user'; + }, + @{ + 'Caption' = 'Install JEA Profile with managed user "IcingaForWindows"'; + 'Command' = 'Show-IcingaForWindowsInstallerConfigurationSummary'; + 'Help' = 'Installs the Icinga for Windows JEA profile with a newly created, managed user "IcingaForWindows". This will override your service and service password configuration'; + }, + @{ + 'Caption' = 'Do not install JEA Profile'; + 'Command' = 'Show-IcingaForWindowsInstallerConfigurationSummary'; + 'Help' = 'Do not install the Icinga for Windows JEA profile'; + } + ) ` + -DefaultIndex $DefaultInput ` + -JumpToSummary:$FALSE ` + -ConfigElement ` + -Automated:$Automated ` + -Advanced:$Advanced; +} + +Set-Alias -Name 'IfW-InstallJEAProfile' -Value 'Show-IcingaForWindowsInstallerMenuSelectInstallJEAProfile'; diff --git a/lib/core/installer/menu/installation/ifwservice/SelectInstallPowerShellService.psm1 b/lib/core/installer/menu/installation/ifwservice/SelectInstallPowerShellService.psm1 new file mode 100644 index 00000000..62584747 --- /dev/null +++ b/lib/core/installer/menu/installation/ifwservice/SelectInstallPowerShellService.psm1 @@ -0,0 +1,32 @@ +function Show-IcingaForWindowsInstallerMenuSelectInstallIcingaForWindowsService() +{ + param ( + [array]$Value = @(), + [string]$DefaultInput = '0', + [switch]$JumpToSummary = $FALSE, + [switch]$Automated = $FALSE, + [switch]$Advanced = $FALSE + ); + + Show-IcingaForWindowsInstallerMenu ` + -Header 'Please select of you want to install the Icinga for Windows service:' ` + -Entries @( + @{ + 'Caption' = 'Install Icinga for Windows Service'; + 'Command' = 'Show-IcingaForWindowsInstallerConfigurationSummary'; + 'Help' = 'Installs the Icinga for Windows service from the provided stable repository'; + }, + @{ + 'Caption' = 'Do not install Icinga for Windows service'; + 'Command' = 'Show-IcingaForWindowsInstallerConfigurationSummary'; + 'Help' = 'Select this if you do not want to install the Icinga for Windows service'; + } + ) ` + -DefaultIndex $DefaultInput ` + -JumpToSummary:$FALSE ` + -ConfigElement ` + -Automated:$Automated ` + -Advanced:$Advanced; +} + +Set-Alias -Name 'IfW-InstallPowerShellService' -Value 'Show-IcingaForWindowsInstallerMenuSelectInstallIcingaForWindowsService'; diff --git a/lib/core/installer/menu/installation/ifwservice/SelectWindowsServiceSource.psm1 b/lib/core/installer/menu/installation/ifwservice/SelectWindowsServiceSource.psm1 deleted file mode 100644 index 754bc5a1..00000000 --- a/lib/core/installer/menu/installation/ifwservice/SelectWindowsServiceSource.psm1 +++ /dev/null @@ -1,42 +0,0 @@ -function Show-IcingaForWindowsInstallerMenuSelectWindowsServiceSource() -{ - param ( - [array]$Value = @(), - [string]$DefaultInput = '0', - [switch]$JumpToSummary = $FALSE, - [switch]$Automated = $FALSE, - [switch]$Advanced = $FALSE - ); - - Show-IcingaForWindowsInstallerMenu ` - -Header 'Please select where the Icinga for Windows service binary is downloaded from:' ` - -Entries @( - @{ - 'Caption' = 'Download latest release from GitHub'; - 'Command' = 'Show-IcingaForWindowsInstallerConfigurationSummary'; - 'Help' = 'Download the latest stable release of the service binary directly from "https://github.com/Icinga/icinga-powershell-service/releases"'; - }, - @{ - 'Caption' = 'Use custom source'; - 'Command' = 'Show-IcingaForWindowsInstallerMenuEnterWindowsServicePackageSource'; - 'Help' = 'Specify a custom location from where to get the Icinga for Windows service package'; - }, - @{ - 'Caption' = 'Do not install service'; - 'Command' = 'Show-IcingaForWindowsInstallerConfigurationSummary'; - 'Help' = 'Select this if you do not want to install the Icinga for Windows service'; - } - ) ` - -DefaultIndex $DefaultInput ` - -JumpToSummary:$FALSE ` - -ConfigElement ` - -Automated:$Automated ` - -Advanced:$Advanced; - - # In case we use the default location, delete our custom location entry - if (Get-IcingaForWindowsManagementConsoleLastInput -ne '1') { - Remove-IcingaForWindowsInstallerConfigEntry -Menu 'Show-IcingaForWindowsInstallerMenuEnterWindowsServicePackageSource'; - } -} - -Set-Alias -Name 'IfW-WindowsServiceSource' -Value 'Show-IcingaForWindowsInstallerMenuSelectWindowsServiceSource'; diff --git a/lib/core/installer/menu/installation/plugins/InstallIcingaPlugins.psm1 b/lib/core/installer/menu/installation/plugins/InstallIcingaPlugins.psm1 new file mode 100644 index 00000000..6cf2b8da --- /dev/null +++ b/lib/core/installer/menu/installation/plugins/InstallIcingaPlugins.psm1 @@ -0,0 +1,32 @@ +function Show-IcingaForWindowsInstallerMenuSelectInstallIcingaPlugins() +{ + param ( + [array]$Value = @(), + [string]$DefaultInput = '0', + [switch]$JumpToSummary = $FALSE, + [switch]$Automated = $FALSE, + [switch]$Advanced = $FALSE + ); + + Show-IcingaForWindowsInstallerMenu ` + -Header 'Please select where your Icinga plugins are downloaded from:' ` + -Entries @( + @{ + 'Caption' = 'Install plugins'; + 'Command' = 'Show-IcingaForWindowsInstallerConfigurationSummary'; + 'Help' = 'Installs the Icinga Plugins from the defined stable repository'; + }, + @{ + 'Caption' = 'Do not install plugins'; + 'Command' = 'Show-IcingaForWindowsInstallerConfigurationSummary'; + 'Help' = 'Select this if you do not want to install the plugins for the moment'; + } + ) ` + -DefaultIndex $DefaultInput ` + -JumpToSummary:$FALSE ` + -ConfigElement ` + -Automated:$Automated ` + -Advanced:$Advanced; +} + +Set-Alias -Name 'IfW-InstallPlugins' -Value 'Show-IcingaForWindowsInstallerMenuSelectInstallIcingaPlugins'; diff --git a/lib/core/installer/menu/installation/plugins/SelectIcingaPluginsSource.psm1 b/lib/core/installer/menu/installation/plugins/SelectIcingaPluginsSource.psm1 deleted file mode 100644 index 84a27436..00000000 --- a/lib/core/installer/menu/installation/plugins/SelectIcingaPluginsSource.psm1 +++ /dev/null @@ -1,47 +0,0 @@ -function Show-IcingaForWindowsInstallerMenuSelectIcingaPluginsSource() -{ - param ( - [array]$Value = @(), - [string]$DefaultInput = '0', - [switch]$JumpToSummary = $FALSE, - [switch]$Automated = $FALSE, - [switch]$Advanced = $FALSE - ); - - Show-IcingaForWindowsInstallerMenu ` - -Header 'Please select where your Icinga plugins are downloaded from:' ` - -Entries @( - @{ - 'Caption' = 'Download latest release from GitHub'; - 'Command' = 'Show-IcingaForWindowsInstallerConfigurationSummary'; - 'Help' = 'Downloads the latest stable release directly from "https://github.com/icinga/icinga-powershell-plugins/releases"'; - }, - @{ - 'Caption' = 'Download snapshot from GitHub'; - 'Command' = 'Show-IcingaForWindowsInstallerConfigurationSummary'; - 'Help' = 'Uses the master branch of the plugin repository for checkout. Not recommended in production'; - }, - @{ - 'Caption' = 'Use custom source'; - 'Command' = 'Show-IcingaForWindowsInstallerMenuEnterPluginsPackageSource'; - 'Help' = 'Specify a custom location from where to get your plugins from'; - }, - @{ - 'Caption' = 'Do not install plugins'; - 'Command' = 'Show-IcingaForWindowsInstallerConfigurationSummary'; - 'Help' = 'Select this if you do not want to install the plugins for the moment'; - } - ) ` - -DefaultIndex $DefaultInput ` - -JumpToSummary:$FALSE ` - -ConfigElement ` - -Automated:$Automated ` - -Advanced:$Advanced; - - # In case we use the default location, delete our custom location entry - if (Get-IcingaForWindowsManagementConsoleLastInput -ne '2') { - Remove-IcingaForWindowsInstallerConfigEntry -Menu 'Show-IcingaForWindowsInstallerMenuEnterPluginsPackageSource'; - } -} - -Set-Alias -Name 'IfW-PluginSource' -Value 'Show-IcingaForWindowsInstallerMenuSelectIcingaPluginsSource'; diff --git a/lib/core/installer/menu/manage/general/InstallComponents.psm1 b/lib/core/installer/menu/manage/general/InstallComponents.psm1 new file mode 100644 index 00000000..f71c0a30 --- /dev/null +++ b/lib/core/installer/menu/manage/general/InstallComponents.psm1 @@ -0,0 +1,52 @@ +function Show-IcingaForWindowsMenuInstallComponents() +{ + $IcingaInstallation = Get-IcingaComponentList; + $CurrentComponents = Get-IcingaInstallation -Release; + [int]$MaxComponentLength = Get-IcingaMaxTextLength -TextArray $IcingaInstallation.Components.Keys; + [array]$InstallList = @(); + + foreach ($entry in $IcingaInstallation.Components.Keys) { + $LatestVersion = $IcingaInstallation.Components[$entry]; + $LockedVersion = Get-IcingaComponentLock -Name $entry; + $VersionText = $LatestVersion; + + # Only show not installed components + if ($CurrentComponents.ContainsKey($entry)) { + continue; + } + + if ($null -ne $LockedVersion) { + $VersionText = [string]::Format('{0}*', $LockedVersion); + $LatestVersion = $LockedVersion; + } + + $InstallList += @{ + 'Caption' = ([string]::Format('{0} [{1}]', (Add-IcingaWhiteSpaceToString -Text $entry -Length $MaxComponentLength), $VersionText)); + 'Command' = 'Show-IcingaForWindowsMenuInstallComponents'; + 'Help' = ([string]::Format('This will install the component "{0}" with version "{1}"', $entry, $VersionText)); + 'Disabled' = $FALSE; + 'Action' = @{ + 'Command' = 'Show-IcingaWindowsManagementConsoleYesNoDialog'; + 'Arguments' = @{ + '-Caption' = ([string]::Format('Install component "{0}" with version "{1}"', $entry, $VersionText)); + '-Command' = 'Install-IcingaComponent'; + '-CmdArguments' = @{ + '-Name' = $entry; + '-Version' = $LatestVersion; + '-Release' = $TRUE; + '-Confirm' = $TRUE; + } + } + } + } + } + + if ($InstallList.Count -ne 0) { + Show-IcingaForWindowsInstallerMenu ` + -Header 'Install Icinga for Windows components. Select an entry to continue:' ` + -Entries $InstallList; + } else { + Show-IcingaForWindowsInstallerMenu ` + -Header 'There are no packages found for installation' + } +} diff --git a/lib/core/installer/menu/manage/general/RemoveComponents.psm1 b/lib/core/installer/menu/manage/general/RemoveComponents.psm1 index e87bc3dd..748e21f7 100644 --- a/lib/core/installer/menu/manage/general/RemoveComponents.psm1 +++ b/lib/core/installer/menu/manage/general/RemoveComponents.psm1 @@ -1,9 +1,11 @@ function Show-IcingaForWindowsMenuRemoveComponents() { - [array]$UninstallFeatures = @(); - $AgentInstalled = Get-Service -Name 'icinga2' -ErrorAction SilentlyContinue; - $ModuleList = Get-Module 'icinga-powershell-*' -ListAvailable; + [array]$UninstallFeatures = @(); + $AgentInstalled = Get-Service -Name 'icinga2' -ErrorAction SilentlyContinue; + $PowerShellServiceInstalled = Get-Service -Name 'icingapowershell' -ErrorAction SilentlyContinue; + $IcingaWindowsServiceData = Get-IcingaForWindowsServiceData; + $ModuleList = Get-Module 'icinga-powershell-*' -ListAvailable; $UninstallFeatures += @{ 'Caption' = 'Uninstall Icinga Agent'; @@ -13,8 +15,11 @@ function Show-IcingaForWindowsMenuRemoveComponents() 'Action' = @{ 'Command' = 'Show-IcingaWindowsManagementConsoleYesNoDialog'; 'Arguments' = @{ - '-Caption' = 'Uninstall Icinga Agent'; - '-Command' = 'Uninstall-IcingaAgent'; + '-Caption' = 'Uninstall Icinga Agent'; + '-Command' = 'Uninstall-IcingaComponent'; + '-CmdArguments' = @{ + '-Name' = 'agent'; + } } } } @@ -28,9 +33,45 @@ function Show-IcingaForWindowsMenuRemoveComponents() 'Command' = 'Show-IcingaWindowsManagementConsoleYesNoDialog'; 'Arguments' = @{ '-Caption' = 'Uninstall Icinga Agent (include ProgramData)'; - '-Command' = 'Uninstall-IcingaAgent'; + '-Command' = 'Uninstall-IcingaComponent'; + '-CmdArguments' = @{ + '-Name' = 'agent'; + '-RemovePackageFiles' = $TRUE; + } + } + } + } + + $UninstallFeatures += @{ + 'Caption' = 'Uninstall Icinga for Windows Service'; + 'Command' = 'Show-IcingaForWindowsMenuRemoveComponents'; + 'Help' = 'This will remove the icingapowershell service for Icinga for Windows if installed' + 'Disabled' = ($null -eq $PowerShellServiceInstalled); + 'Action' = @{ + 'Command' = 'Show-IcingaWindowsManagementConsoleYesNoDialog'; + 'Arguments' = @{ + '-Caption' = 'Uninstall Icinga for Windows service'; + '-Command' = 'Uninstall-IcingaComponent'; + '-CmdArguments' = @{ + '-Name' = 'service'; + } + } + } + } + + $UninstallFeatures += @{ + 'Caption' = 'Uninstall Icinga for Windows Service (include files)'; + 'Command' = 'Show-IcingaForWindowsMenuRemoveComponents'; + 'Help' = 'This will remove the icingapowershell service for Icinga for Windows if installed and the service binary including the folder, if empty afterwards' + 'Disabled' = (-Not (Test-Path $IcingaWindowsServiceData.Directory)); + 'Action' = @{ + 'Command' = 'Show-IcingaWindowsManagementConsoleYesNoDialog'; + 'Arguments' = @{ + '-Caption' = 'Uninstall Icinga for Windows service (include files)'; + '-Command' = 'Uninstall-IcingaComponent'; '-CmdArguments' = @{ - '-RemoveDataFolder' = $TRUE; + '-Name' = 'service'; + '-RemovePackageFiles' = $TRUE; } } } @@ -40,7 +81,7 @@ function Show-IcingaForWindowsMenuRemoveComponents() $ComponentName = $module.Name.Replace('icinga-powershell-', ''); $Caption = ([string]::Format('Uninstall component "{0}"', $ComponentName)); - if ($ComponentName -eq 'framework') { + if ($ComponentName -eq 'framework' -Or $ComponentName -eq 'service' -Or $ComponentName -eq 'agent') { continue; } @@ -53,7 +94,7 @@ function Show-IcingaForWindowsMenuRemoveComponents() 'Command' = 'Show-IcingaWindowsManagementConsoleYesNoDialog'; 'Arguments' = @{ '-Caption' = $Caption; - '-Command' = 'Uninstall-IcingaFrameworkComponent'; + '-Command' = 'Uninstall-IcingaComponent'; '-CmdArguments' = @{ '-Name' = $ComponentName; } diff --git a/lib/core/installer/menu/manage/general/UpdateComponents.psm1 b/lib/core/installer/menu/manage/general/UpdateComponents.psm1 new file mode 100644 index 00000000..9140d8ad --- /dev/null +++ b/lib/core/installer/menu/manage/general/UpdateComponents.psm1 @@ -0,0 +1,68 @@ +function Show-IcingaForWindowsMenuUpdateComponents() +{ + $IcingaInstallation = Get-IcingaInstallation -Release; + [int]$MaxComponentLength = Get-IcingaMaxTextLength -TextArray $IcingaInstallation.Keys; + [array]$UpdateList = @(); + + foreach ($entry in $IcingaInstallation.Keys) { + $Component = $IcingaInstallation[$entry]; + + $LatestVersion = $Component.LatestVersion; + if ([string]::IsNullOrEmpty($Component.LockedVersion) -eq $FALSE) { + if ([Version]$Component.CurrentVersion -ge [Version]$Component.LockedVersion) { + continue; + } + $LatestVersion = [string]::Format('{0}*', $Component.LockedVersion); + } + + if ([string]::IsNullOrEmpty($LatestVersion)) { + continue; + } + + $UpdateList += @{ + 'Caption' = ([string]::Format('{0} [{1}] => [{2}]', (Add-IcingaWhiteSpaceToString -Text $entry -Length $MaxComponentLength), $Component.CurrentVersion, $LatestVersion)); + 'Command' = 'Show-IcingaForWindowsMenuUpdateComponents'; + 'Help' = ([string]::Format('This will update the component "{0}" from current version "{1}" to stable version "{2}"', $entry, $Component.CurrentVersion, $LatestVersion)); + 'Disabled' = $FALSE; + 'Action' = @{ + 'Command' = 'Show-IcingaWindowsManagementConsoleYesNoDialog'; + 'Arguments' = @{ + '-Caption' = ([string]::Format('Update component "{0}" from version "{1}" to stable version "{2}"', $entry, $Component.CurrentVersion, $LatestVersion)); + '-Command' = 'Update-Icinga'; + '-CmdArguments' = @{ + '-Name' = $entry; + '-Release' = $TRUE; + '-Confirm' = $TRUE; + } + } + } + } + } + + if ($UpdateList.Count -ne 0) { + $UpdateList += @{ + 'Caption' = 'Update entire environment'; + 'Command' = 'Show-IcingaForWindowsMenuUpdateComponents'; + 'Help' = 'This will update all components listed above to the mentioned stable version' + 'Disabled' = $FALSE; + 'Action' = @{ + 'Command' = 'Show-IcingaWindowsManagementConsoleYesNoDialog'; + 'Arguments' = @{ + '-Caption' = 'Update entire Icinga for Windows environment'; + '-Command' = 'Update-Icinga'; + '-CmdArguments' = @{ + '-Release' = $TRUE; + '-Confirm' = $TRUE; + } + } + } + } + + Show-IcingaForWindowsInstallerMenu ` + -Header 'Updates Icinga for Windows components. Select an entry to continue:' ` + -Entries $UpdateList; + } else { + Show-IcingaForWindowsInstallerMenu ` + -Header 'There are no updates pending for your environment' + } +} diff --git a/lib/core/installer/tools/GetPowerShellServicePassword.psm1 b/lib/core/installer/tools/GetPowerShellServicePassword.psm1 new file mode 100644 index 00000000..4da68ac3 --- /dev/null +++ b/lib/core/installer/tools/GetPowerShellServicePassword.psm1 @@ -0,0 +1,8 @@ +function Get-IcingaInternalPowerShellServicePassword() +{ + if ($null -eq $global:Icinga -Or $Global:Icinga.ContainsKey('InstallerServicePassword') -eq $FALSE) { + return $null; + } + + return $Global:Icinga.InstallerServicePassword; +} diff --git a/lib/core/installer/tools/SetPowerShellServicePassword.psm1 b/lib/core/installer/tools/SetPowerShellServicePassword.psm1 new file mode 100644 index 00000000..fd692a61 --- /dev/null +++ b/lib/core/installer/tools/SetPowerShellServicePassword.psm1 @@ -0,0 +1,25 @@ +function Set-IcingaInternalPowerShellServicePassword() +{ + param ( + [SecureString]$Password = $null + ); + + if ($null -eq $global:Icinga) { + $Global:Icinga = @{ + 'InstallerServicePassword' = $Password; + } + + return; + } + + if ($Global:Icinga.ContainsKey('InstallerServicePassword') -eq $FALSE) { + $Global:Icinga.Add( + 'InstallerServicePassword', + $Password + ) + + return; + } + + $Global:Icinga.InstallerServicePassword = $Password; +} diff --git a/lib/core/repository/Add-IcingaRepository.psm1 b/lib/core/repository/Add-IcingaRepository.psm1 new file mode 100644 index 00000000..670d2214 --- /dev/null +++ b/lib/core/repository/Add-IcingaRepository.psm1 @@ -0,0 +1,43 @@ +function Add-IcingaRepository() +{ + param ( + [string]$Name = $null, + [string]$RemotePath = $null + ); + + if ([string]::IsNullOrEmpty($Name)) { + Write-IcingaConsoleError 'You have to provide a name for the repository'; + return; + } + + if ([string]::IsNullOrEmpty($RemotePath)) { + Write-IcingaConsoleError 'You have to provide a remote path for the repository'; + return; + } + + $CurrentRepositories = Get-IcingaPowerShellConfig -Path 'Framework.Repository.Repositories'; + + if ($null -eq $CurrentRepositories) { + $CurrentRepositories = New-Object -TypeName PSObject; + } + + if (Test-IcingaPowerShellConfigItem -ConfigObject $CurrentRepositories -ConfigKey $Name) { + Write-IcingaConsoleError 'A repository with the given name "{0}" does already exist.' -Objects $Name; + return; + } + + [array]$RepoCount = $CurrentRepositories.PSObject.Properties.Count; + + $CurrentRepositories | Add-Member -MemberType NoteProperty -Name $Name -Value (New-Object -TypeName PSObject); + $CurrentRepositories.$Name | Add-Member -MemberType NoteProperty -Name 'LocalPath' -Value $null; + $CurrentRepositories.$Name | Add-Member -MemberType NoteProperty -Name 'RemotePath' -Value $RemotePath; + $CurrentRepositories.$Name | Add-Member -MemberType NoteProperty -Name 'CloneSource' -Value $null; + $CurrentRepositories.$Name | Add-Member -MemberType NoteProperty -Name 'UseSCP' -Value $FALSE; + $CurrentRepositories.$Name | Add-Member -MemberType NoteProperty -Name 'Order' -Value $RepoCount.Count; + $CurrentRepositories.$Name | Add-Member -MemberType NoteProperty -Name 'Enabled' -Value $True; + + Set-IcingaPowerShellConfig -Path 'Framework.Repository.Repositories' -Value $CurrentRepositories; + Push-IcingaRepository -Name $Name -Silent; + + Write-IcingaConsoleNotice 'Remote repository "{0}" was successfully added' -Objects $Name; +} diff --git a/lib/core/repository/Disable-IcingaRepository.psm1 b/lib/core/repository/Disable-IcingaRepository.psm1 new file mode 100644 index 00000000..bca6521b --- /dev/null +++ b/lib/core/repository/Disable-IcingaRepository.psm1 @@ -0,0 +1,27 @@ +function Disable-IcingaRepository() +{ + param ( + [string]$Name = $null + ); + + if ([string]::IsNullOrEmpty($Name)) { + Write-IcingaConsoleError 'You have to provide a name for the repository'; + return; + } + + $CurrentRepositories = Get-IcingaPowerShellConfig -Path ([string]::Format('Framework.Repository.Repositories.{0}', $Name)); + + if ($null -eq $CurrentRepositories) { + Write-IcingaConsoleError 'A repository with the name "{0}" is not configured' -Objects $Name; + return; + } + + if ($CurrentRepositories.Enabled -eq $FALSE) { + Write-IcingaConsoleNotice 'The repository "{0}" is already disabled' -Objects $Name; + return; + } + + Set-IcingaPowerShellConfig -Path ([string]::Format('Framework.Repository.Repositories.{0}.Enabled', $Name)) -Value $FALSE; + + Write-IcingaConsoleNotice 'The repository "{0}" was successfully disabled' -Objects $Name; +} diff --git a/lib/core/repository/Enable-IcingaRepository.psm1 b/lib/core/repository/Enable-IcingaRepository.psm1 new file mode 100644 index 00000000..8919631b --- /dev/null +++ b/lib/core/repository/Enable-IcingaRepository.psm1 @@ -0,0 +1,27 @@ +function Enable-IcingaRepository() +{ + param ( + [string]$Name = $null + ); + + if ([string]::IsNullOrEmpty($Name)) { + Write-IcingaConsoleError 'You have to provide a name for the repository'; + return; + } + + $CurrentRepositories = Get-IcingaPowerShellConfig -Path ([string]::Format('Framework.Repository.Repositories.{0}', $Name)); + + if ($null -eq $CurrentRepositories) { + Write-IcingaConsoleError 'A repository with the name "{0}" is not configured' -Objects $Name; + return; + } + + if ($CurrentRepositories.Enabled -eq $TRUE) { + Write-IcingaConsoleNotice 'The repository "{0}" is already enabled' -Objects $Name; + return; + } + + Set-IcingaPowerShellConfig -Path ([string]::Format('Framework.Repository.Repositories.{0}.Enabled', $Name)) -Value $TRUE; + + Write-IcingaConsoleNotice 'The repository "{0}" was successfully enabled' -Objects $Name; +} diff --git a/lib/core/repository/Get-IcingaComponentList.psm1 b/lib/core/repository/Get-IcingaComponentList.psm1 new file mode 100644 index 00000000..8a1ec7f4 --- /dev/null +++ b/lib/core/repository/Get-IcingaComponentList.psm1 @@ -0,0 +1,64 @@ +function Get-IcingaComponentList() +{ + param ( + [switch]$Snapshot = $FALSE + ); + + $Repositories = Get-IcingaRepositories -ExcludeDisabled; + [Version]$LatestVersion = $null; + [string]$SourcePath = $null; + [bool]$FoundPackage = $FALSE; + [array]$Output = @(); + [bool]$FoundPackage = $FALSE; + + $SearchList = New-Object -TypeName PSObject; + $SearchList | Add-Member -MemberType NoteProperty -Name 'Repos' -Value @(); + $SearchList | Add-Member -MemberType NoteProperty -Name 'Components' -Value @{ }; + + foreach ($entry in $Repositories) { + $RepoContent = Read-IcingaRepositoryFile -Name $entry.Name; + + if ($null -eq $RepoContent) { + continue; + } + + if ((Test-IcingaPowerShellConfigItem -ConfigObject $RepoContent -ConfigKey 'Packages') -eq $FALSE) { + continue; + } + + foreach ($repoEntry in $RepoContent.Packages.PSObject.Properties.Name) { + + $RepoData = New-Object -TypeName PSObject; + $RepoData | Add-Member -MemberType NoteProperty -Name 'Name' -Value $entry.Name; + $RepoData | Add-Member -MemberType NoteProperty -Name 'RemoteSource' -Value $RepoContent.Info.RemoteSource; + $RepoData | Add-Member -MemberType NoteProperty -Name 'ComponentName' -Value $repoEntry; + $RepoData | Add-Member -MemberType NoteProperty -Name 'Packages' -Value @(); + + foreach ($package in $RepoContent.Packages.$repoEntry) { + + $ComponentData = New-Object -TypeName PSObject; + $ComponentData | Add-Member -MemberType NoteProperty -Name 'Version' -Value $package.Version; + $ComponentData | Add-Member -MemberType NoteProperty -Name 'Location' -Value $package.Location; + $ComponentData | Add-Member -MemberType NoteProperty -Name 'Snapshot' -Value $package.Snapshot; + + if ($Snapshot -And $package.Snapshot -eq $FALSE) { + continue; + } + + if ($SearchList.Components.ContainsKey($repoEntry) -eq $FALSE) { + $SearchList.Components.Add($repoEntry, $package.Version); + } + + if ([version]($SearchList.Components[$repoEntry]) -lt [version]$package.Version) { + $SearchList.Components[$repoEntry] = $package.Version; + } + + $RepoData.Packages += $ComponentData; + } + + $SearchList.Repos += $RepoData; + } + } + + return $SearchList; +} diff --git a/lib/core/repository/Get-IcingaComponentLock.psm1 b/lib/core/repository/Get-IcingaComponentLock.psm1 new file mode 100644 index 00000000..a1319820 --- /dev/null +++ b/lib/core/repository/Get-IcingaComponentLock.psm1 @@ -0,0 +1,23 @@ +function Get-IcingaComponentLock() +{ + param ( + [string]$Name = $null + ); + + if ([string]::IsNullOrEmpty($Name)) { + Write-IcingaConsoleError 'You have to specify the component to get the lock version'; + return; + } + + $LockedComponents = Get-IcingaPowerShellConfig -Path 'Framework.Repository.ComponentLock'; + + if ($null -eq $LockedComponents) { + return $null; + } + + if (Test-IcingaPowerShellConfigItem -ConfigObject $LockedComponents -ConfigKey $Name) { + return $LockedComponents.$Name; + } + + return $null; +} diff --git a/lib/core/repository/Get-IcingaForWindowsServiceData.psm1 b/lib/core/repository/Get-IcingaForWindowsServiceData.psm1 new file mode 100644 index 00000000..265ea39e --- /dev/null +++ b/lib/core/repository/Get-IcingaForWindowsServiceData.psm1 @@ -0,0 +1,26 @@ +function Get-IcingaForWindowsServiceData() +{ + $IcingaForWindowsService = Get-IcingaServices -Service 'icingapowershell'; + + [hashtable]$ServiceData = @{ + 'Directory' = ''; + 'FullPath' = ''; + 'User' = ''; + } + + if ($null -ne $IcingaForWindowsService) { + $ServicePath = $IcingaForWindowsService.icingapowershell.configuration.ServicePath; + $ServicePath = $ServicePath.SubString(0, $ServicePath.IndexOf('.exe') + 4); + $ServicePath = $ServicePath.Replace('"', ''); + $ServiceData.FullPath = $ServicePath; + $ServiceData.Directory = $ServicePath.Substring(0, $ServicePath.LastIndexOf('\') + 1); + $ServiceData.User = $IcingaForWindowsService.icingapowershell.configuration.ServiceUser; + + return $ServiceData; + } + + $ServiceData.Directory = (Join-Path -Path $env:ProgramFiles -ChildPath 'icinga-framework-service'); + $ServiceData.User = 'NT Authority\NetworkService'; + + return $ServiceData; +} diff --git a/lib/core/repository/Get-IcingaInstallation.psm1 b/lib/core/repository/Get-IcingaInstallation.psm1 new file mode 100644 index 00000000..61dd3846 --- /dev/null +++ b/lib/core/repository/Get-IcingaInstallation.psm1 @@ -0,0 +1,108 @@ +function Get-IcingaInstallation() +{ + param ( + [switch]$Release = $FALSE, + [switch]$Snapshot = $FALSE + ) + [hashtable]$InstalledComponents = @{ }; + + $PowerShellModules = Get-Module -ListAvailable; + + foreach ($entry in $PowerShellModules) { + $RootPath = (Get-IcingaForWindowsRootPath); + + if ($entry.Path -NotLike "$RootPath*") { + continue; + } + + if ($entry.Name -Like 'icinga-powershell-*') { + $ComponentName = $entry.Name.Replace('icinga-powershell-', ''); + $InstallPackage = (Get-IcingaRepositoryPackage -Name $ComponentName -Release:$Release -Snapshot:$Snapshot); + $LatestVersion = ''; + $CurrentVersion = ([string]((Get-Module -ListAvailable -Name $entry.Name -ErrorAction SilentlyContinue) | Sort-Object Version -Descending | Select-Object Version -First 1).Version); + + if ($InstallPackage.HasPackage) { + [string]$LatestVersion = $InstallPackage.Package.Version; + } + + if ([string]::IsNullOrEmpty($LatestVersion) -eq $FALSE -And [Version]$LatestVersion -le [Version]$CurrentVersion) { + $LatestVersion = ''; + } + + Add-IcingaHashtableItem ` + -Hashtable $InstalledComponents ` + -Key $ComponentName ` + -Value @{ + 'Path' = (Join-Path -Path $RootPath -ChildPath $entry.Name); + 'CurrentVersion' = $CurrentVersion; + 'LatestVersion' = $LatestVersion; + 'LockedVersion' = (Get-IcingaComponentLock -Name $ComponentName); + } | Out-Null; + } + } + + $IcingaForWindowsService = Get-IcingaServices -Service 'icingapowershell'; + + if ($null -ne $IcingaForWindowsService) { + $ServicePath = Get-IcingaForWindowsServiceData; + + if ($InstalledComponents.ContainsKey('service')) { + $InstalledComponents.Remove('service'); + } + + $InstallPackage = (Get-IcingaRepositoryPackage -Name 'service' -Release:$Release -Snapshot:$Snapshot); + $LatestVersion = ''; + $CurrentVersion = ([string]((Read-IcingaServicePackage -File $ServicePath.FullPath).ProductVersion)); + + if ($InstallPackage.HasPackage) { + [string]$LatestVersion = $InstallPackage.Package.Version; + } + + if ([string]::IsNullOrEmpty($LatestVersion) -eq $FALSE -And [Version]$LatestVersion -le [Version]$CurrentVersion) { + $LatestVersion = ''; + } + + $InstalledComponents.Add( + 'service', + @{ + 'Path' = $ServicePath.Directory; + 'CurrentVersion' = $CurrentVersion; + 'LatestVersion' = $LatestVersion; + 'LockedVersion' = (Get-IcingaComponentLock -Name 'service'); + } + ) + } + + $IcingaAgent = Get-IcingaAgentInstallation; + + if ($InstalledComponents.ContainsKey('agent')) { + $InstalledComponents.Remove('agent'); + } + + if ($IcingaAgent.Installed) { + + $InstallPackage = (Get-IcingaRepositoryPackage -Name 'agent' -Release:$Release -Snapshot:$Snapshot); + $LatestVersion = ''; + $CurrentVersion = ([string]$IcingaAgent.Version.Full); + + if ($InstallPackage.HasPackage) { + $LatestVersion = $InstallPackage.Package.Version; + } + + if ([string]::IsNullOrEmpty($LatestVersion) -eq $FALSE -And [Version]$LatestVersion -le [Version]$CurrentVersion) { + $LatestVersion = ''; + } + + $InstalledComponents.Add( + 'agent', + @{ + 'Path' = $IcingaAgent.RootDir; + 'CurrentVersion' = $CurrentVersion; + 'LatestVersion' = $LatestVersion; + 'LockedVersion' = (Get-IcingaComponentLock -Name 'agent'); + } + ) + } + + return $InstalledComponents; +} diff --git a/lib/core/repository/Get-IcingaRepositories.psm1 b/lib/core/repository/Get-IcingaRepositories.psm1 new file mode 100644 index 00000000..62559970 --- /dev/null +++ b/lib/core/repository/Get-IcingaRepositories.psm1 @@ -0,0 +1,25 @@ +function Get-IcingaRepositories() +{ + param ( + [switch]$ExcludeDisabled = $FALSE + ); + + $CurrentRepositories = Get-IcingaPowerShellConfig -Path 'Framework.Repository.Repositories'; + [array]$RepoList = $CurrentRepositories.PSObject.Properties | Sort-Object { $_.Value.Order } -Descending; + + if ($ExcludeDisabled -eq $FALSE) { + return $RepoList; + } + + [array]$ActiveRepos = @(); + + foreach ($repo in $RepoList) { + if ($repo.Value.Enabled -eq $FALSE) { + continue; + } + + $ActiveRepos += $repo; + } + + return $ActiveRepos; +} diff --git a/lib/core/repository/Get-IcingaRepositoryHash.psm1 b/lib/core/repository/Get-IcingaRepositoryHash.psm1 new file mode 100644 index 00000000..31d1a01d --- /dev/null +++ b/lib/core/repository/Get-IcingaRepositoryHash.psm1 @@ -0,0 +1,23 @@ +function Get-IcingaRepositoryHash() +{ + param ( + [string]$Path + ); + + if ([string]::IsNullOrEmpty($Path) -Or (Test-Path $Path) -eq $FALSE) { + Write-IcingaConsoleError 'The provided path "{0}" does not exist' -Objects $Path; + return; + } + + $RepositoryFolder = Get-ChildItem -Path $Path -Recurse; + [array]$FileHashes = @(); + + foreach ($entry in $RepositoryFolder) { + $FileHashes += (Get-FileHash -Path $entry.FullName -Algorithm SHA256).Hash; + } + + $HashAlgorithm = [System.Security.Cryptography.HashAlgorithm]::Create('SHA256'); + $BinaryHash = $HashAlgorithm.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($FileHashes.ToString())) + + return [System.BitConverter]::ToString($BinaryHash).Replace('-', ''); +} diff --git a/lib/core/repository/Get-IcingaRepositoryPackage.psm1 b/lib/core/repository/Get-IcingaRepositoryPackage.psm1 new file mode 100644 index 00000000..f86da177 --- /dev/null +++ b/lib/core/repository/Get-IcingaRepositoryPackage.psm1 @@ -0,0 +1,78 @@ +function Get-IcingaRepositoryPackage() +{ + param ( + [string]$Name, + [string]$Version = $null, + [switch]$Release = $FALSE, + [switch]$Snapshot = $FALSE + ); + + if ([string]::IsNullOrEmpty($Name)) { + Write-IcingaConsoleError 'You have to provide a component name'; + return; + } + + $Repositories = Get-IcingaRepositories -ExcludeDisabled; + [Version]$LatestVersion = $null; + $InstallPackage = $null; + $SourceRepo = $null; + $RepoName = $null; + [bool]$HasRepo = $FALSE; + + foreach ($entry in $Repositories) { + $RepoContent = Read-IcingaRepositoryFile -Name $entry.Name; + [bool]$FoundPackage = $FALSE; + + if ($null -eq $RepoContent) { + continue; + } + + if ((Test-IcingaPowerShellConfigItem -ConfigObject $RepoContent -ConfigKey 'Packages') -eq $FALSE) { + continue; + } + + if ((Test-IcingaPowerShellConfigItem -ConfigObject $RepoContent.Packages -ConfigKey $Name) -eq $FALSE) { + continue; + } + + foreach ($package in $RepoContent.Packages.$Name) { + + if ($Snapshot -And $package.Snapshot -eq $FALSE) { + continue; + } + + if ($Release -And $package.Snapshot -eq $TRUE) { + continue; + } + + if ([string]::IsNullOrEmpty($Version) -And ($null -eq $LatestVersion -Or $LatestVersion -lt $package.Version)) { + [Version]$LatestVersion = [Version]$package.Version; + $InstallPackage = $package; + $HasRepo = $TRUE; + $SourceRepo = $RepoContent; + $RepoName = $entry.Name; + continue; + } + + if ([string]::IsNullOrEmpty($Version) -eq $FALSE -And [version]$package.Version -eq [version]$Version) { + $InstallPackage = $package; + $FoundPackage = $TRUE; + $HasRepo = $TRUE; + $SourceRepo = $RepoContent; + $RepoName = $entry.Name; + break; + } + } + + if ($FoundPackage) { + break; + } + } + + return @{ + 'HasPackage' = $HasRepo; + 'Package' = $InstallPackage; + 'Source' = $SourceRepo; + 'Repository' = $RepoName; + }; +} diff --git a/lib/core/repository/Install-IcingaComponent.psm1 b/lib/core/repository/Install-IcingaComponent.psm1 new file mode 100644 index 00000000..f513b93c --- /dev/null +++ b/lib/core/repository/Install-IcingaComponent.psm1 @@ -0,0 +1,354 @@ +function Install-IcingaComponent() +{ + param ( + [string]$Name = $null, + [string]$Version = $null, + [switch]$Release = $FALSE, + [switch]$Snapshot = $FALSE, + [switch]$Confirm = $FALSE, + [switch]$Force = $FALSE + ); + + if ([string]::IsNullOrEmpty($Name)) { + Write-IcingaConsoleError 'You have to provide a component name'; + return; + } + + Set-IcingaTLSVersion; + + if ($Version -eq 'release') { + $Version = $null; + } + + if ($Release -eq $TRUE -And $Snapshot -eq $TRUE) { + Write-IcingaConsoleError 'You can only select either "Release" or "Snapshot" channel for package installation'; + return; + } + + if ($Release -eq $FALSE -And $Snapshot -eq $FALSE) { + $Release = $TRUE; + } + + $LockedVersion = Get-IcingaComponentLock -Name $Name; + + if ($null -ne $LockedVersion) { + $Version = $LockedVersion; + Write-IcingaConsoleNotice 'Component "{0}" is locked to version "{1}"' -Objects $Name, $LockedVersion; + } + + $PackageContent = Get-IcingaRepositoryPackage -Name $Name -Version $Version -Release:$Release -Snapshot:$Snapshot; + $InstallPackage = $PackageContent.Package; + $SourceRepo = $PackageContent.Source; + $RepoName = $PackageContent.Repository; + + if ($PackageContent.HasPackage -eq $FALSE) { + $SearchVersion = 'release'; + if ([string]::IsNullOrEmpty($Version) -eq $FALSE) { + $SearchVersion = $Version; + } + if ($Release) { + Write-IcingaConsoleError 'The component "{0}" was not found on stable channel with version "{1}"' -Objects $Name, $SearchVersion; + return; + } + if ($Snapshot) { + Write-IcingaConsoleError 'The component "{0}" was not found on snapshot channel with version "{1}"' -Objects $Name, $SearchVersion; + return; + } + return; + } + + $FileSource = $InstallPackage.Location; + + if ($InstallPackage.RelativePath -eq $TRUE) { + $FileSource = Join-WebPath -Path ($SourceRepo.Info.RemoteSource.Replace('\', '/')) -ChildPath ($InstallPackage.Location.Replace('\', '/')); + } + + if ($Confirm -eq $FALSE) { + if ((Get-IcingaAgentInstallerAnswerInput -Prompt ([string]::Format('Do you want to install component "{0}" from source "{1}" ({2})?', $Name.ToLower(), $RepoName, $FileSource)) -Default 'y').result -ne 1) { + return; + } + } + + $FileName = $FileSource.SubString($FileSource.LastIndexOf('/') + 1, $FileSource.Length - $FileSource.LastIndexOf('/') - 1); + $DownloadDirectory = New-IcingaTemporaryDirectory; + $DownloadDestination = (Join-Path -Path $DownloadDirectory -ChildPath $FileName); + + Write-IcingaConsoleNotice ([string]::Format('Downloading "{0}" from "{1}"', $Name.ToLower(), $FileSource)); + + if ((Invoke-IcingaWebRequest -UseBasicParsing -Uri $FileSource -OutFile $DownloadDestination).HasErrors) { + Write-IcingaConsoleError ([string]::Format('Failed to download "{0}" from "{1}" into "{2}". Starting cleanup process', $Name.ToLower(), $FileSource, $DownloadDestination)); + Start-Sleep -Seconds 2; + Remove-Item -Path $DownloadDirectory -Recurse -Force; + + return; + } + + $FileHash = (Get-FileHash -Path $DownloadDestination -Algorithm SHA256).Hash; + + if ([string]::IsNullOrEmpty($InstallPackage.Hash) -eq $FALSE -And (Get-FileHash -Path $DownloadDestination -Algorithm SHA256).Hash -ne $InstallPackage.Hash) { + Write-IcingaConsoleError ([string]::Format('File validation failed. The stored hash inside the repository "{0}" is not matching the file hash "{1}"', $InstallPackage.Hash, $FileHash)); + return; + } + + if ([IO.Path]::GetExtension($FileName) -eq '.zip') { + <# + Handles installation of Icinga for Windows packages and Icinga for Windows service + #> + + Expand-IcingaZipArchive -Path $DownloadDestination -Destination $DownloadDirectory | Out-Null; + Start-Sleep -Seconds 2; + Remove-Item -Path $DownloadDestination -Force; + + $FolderContent = Get-ChildItem -Path $DownloadDirectory -Recurse -Include '*.psd1'; + + <# + Handles installation of Icinga for Windows packages + #> + if ($null -ne $FolderContent -And $FolderContent.Count -ne 0) { + $ManifestFile = $null; + $PackageName = $null; + $PackageRoot = $null; + + foreach ($manifest in $FolderContent) { + $ManifestFile = Read-IcingaPackageManifest -File $manifest.FullName; + + if ($null -ne $ManifestFile) { + $PackageName = $manifest.Name.Replace('.psd1', ''); + $PackageRoot = $manifest.FullName.SubString(0, $manifest.FullName.LastIndexOf('\')); + $PackageRoot = Join-Path -Path $PackageRoot -ChildPath '\*' + break; + } + } + + if ($null -eq $ManifestFile) { + Write-IcingaConsoleError ([string]::Format('Unable to read manifest for package "{0}". Aborting installation', $Name.ToLower())); + Start-Sleep -Seconds 2; + Remove-Item -Path $DownloadDirectory -Recurse -Force; + return; + } + + $ComponentFolder = Join-Path -Path (Get-IcingaForWindowsRootPath) -ChildPath $PackageName; + $ModuleData = (Get-Module -ListAvailable -Name $PackageName -ErrorAction SilentlyContinue) | Sort-Object Version -Descending | Select-Object Version -First 1; + [string]$InstallVersion = $null; + $ServiceStatus = $null; + $AgentStatus = $null; + + if ($null -ne $ModuleData) { + [string]$InstallVersion = $ModuleData.Version; + } + + if ($ManifestFile.ModuleVersion -eq $InstallVersion -And $Force -eq $FALSE) { + Write-IcingaConsoleError ([string]::Format('The package "{0}" with version "{1}" is already installed. Use "-Force" to re-install the component', $Name.ToLower(), $ManifestFile.ModuleVersion)); + Start-Sleep -Seconds 2; + Remove-Item -Path $DownloadDirectory -Recurse -Force; + return; + } + + # These update steps only apply for the framework + if ($Name.ToLower() -eq 'framework') { + $ServiceStatus = (Get-Service 'icingapowershell' -ErrorAction SilentlyContinue).Status; + $AgentStatus = (Get-Service 'icinga2' -ErrorAction SilentlyContinue).Status; + + if ($ServiceStatus -eq 'Running') { + Write-IcingaConsoleNotice 'Stopping Icinga for Windows service'; + Stop-IcingaService 'icingapowershell'; + Start-Sleep -Seconds 1; + } + if ($AgentStatus -eq 'Running') { + Write-IcingaConsoleNotice 'Stopping Icinga Agent service'; + Stop-IcingaService 'icinga2'; + Start-Sleep -Seconds 1; + } + } + + if ((Test-Path $ComponentFolder) -eq $FALSE) { + [void](New-Item -ItemType Directory -Path $ComponentFolder -Force); + } + + $ComponentFileContent = Get-ChildItem -Path $ComponentFolder; + + foreach ($entry in $ComponentFileContent) { + if (($entry.Name -eq 'cache' -Or $entry.Name -eq 'config') -And $Name.ToLower() -eq 'framework') { + continue; + } + + [void](Remove-ItemSecure -Path $entry.FullName -Recurse -Force); + } + + [void](Copy-ItemSecure -Path $PackageRoot -Destination $ComponentFolder -Recurse -Force); + + Write-IcingaConsoleNotice 'Installing version "{0}" of component "{1}"' -Objects $ManifestFile.ModuleVersion, $Name.ToLower(); + + Unblock-IcingaPowerShellFiles -Path $ComponentFolder; + + if ($Name.ToLower() -eq 'framework') { + if (Test-IcingaFunction 'Write-IcingaFrameworkCodeCache') { + Write-IcingaFrameworkCodeCache; + } + + if ($ServiceStatus -eq 'Running') { + Write-IcingaConsoleNotice 'Starting Icinga for Windows service'; + Start-IcingaService 'icingapowershell'; + } + if ($AgentStatus -eq 'Running') { + Write-IcingaConsoleNotice 'Starting Icinga Agent service'; + Start-IcingaService 'icinga2'; + } + } + + Import-Module -Name $PackageName -Force; + Write-IcingaConsoleNotice 'Installation of component "{0}" with version "{1}" was successful. Open a new PowerShell to apply the changes' -Objects $Name.ToLower(), $ManifestFile.ModuleVersion; + } else { + <# + Handles installation of Icinga for Windows service + #> + + $FolderContent = Get-ChildItem -Path $DownloadDirectory -Recurse -Include 'icinga-service.exe'; + + if ($Name.ToLower() -eq 'service') { + + $ConfigDirectory = Get-IcingaPowerShellConfig -Path 'Framework.Icinga.IcingaForWindowsService'; + $ConfigUser = Get-IcingaPowerShellConfig -Path 'Framework.Icinga.ServiceUser'; + $ServiceData = Get-IcingaForWindowsServiceData; + $ServiceDirectory = $ServiceData.Directory; + $ServiceUser = $ServiceData.User; + + if ([string]::IsNullOrEmpty($ConfigDirectory) -eq $FALSE) { + $ServiceDirectory = $ConfigDirectory; + } + + if ([string]::IsNullOrEmpty($ConfigUser) -eq $FALSE) { + $ServiceUser = $ConfigUser; + } + + foreach ($binary in $FolderContent) { + + if ((Test-IcingaZipBinaryChecksum -Path $binary.FullName) -eq $FALSE) { + Write-IcingaConsoleError 'The checksum for the given service binary does not match'; + continue; + } + + if ((Test-Path $ServiceDirectory) -eq $FALSE) { + [void](New-Item -ItemType Directory -Path $ServiceDirectory -Force); + } + + $UpdateBin = Join-Path -Path $ServiceDirectory -ChildPath 'icinga-service.exe.update'; + $ServiceBin = Join-Path -Path $ServiceDirectory -ChildPath 'icinga-service.exe'; + + # Service is already installed + if (Test-Path $ServiceBin) { + $InstalledService = Read-IcingaServicePackage -File $ServiceBin; + $NewService = Read-IcingaServicePackage -File $binary.FullName; + + if ($InstalledService.ProductVersion -eq $NewService.ProductVersion -And $null -ne $InstalledService -And $null -ne $NewService -And $Force -eq $FALSE) { + Write-IcingaConsoleError ([string]::Format('The package "service" with version "{0}" is already installed. Use "-Force" to re-install the component', $InstalledService.ProductVersion)); + Start-Sleep -Seconds 2; + Remove-Item -Path $DownloadDirectory -Recurse -Force; + + return; + } + } + + Write-IcingaConsoleNotice 'Installing component "service" into "{0}"' -Objects $ServiceDirectory; + + Copy-ItemSecure -Path $binary.FullName -Destination $UpdateBin -Force; + + [void](Install-IcingaForWindowsService -Path $ServiceBin -User $ServiceUser -Password (Get-IcingaInternalPowerShellServicePassword)); + Set-IcingaInternalPowerShellServicePassword -Password $null; + Start-Sleep -Seconds 2; + Remove-Item -Path $DownloadDirectory -Recurse -Force; + + Write-IcingaConsoleNotice 'Installation of component "service" was successful' + + return; + } + + Write-IcingaConsoleError 'Failed to install component "service". Either the package did not include a service binary or the checksum of the binary did not match'; + Start-Sleep -Seconds 2; + Remove-Item -Path $DownloadDirectory -Recurse -Force; + return; + } else { + Write-IcingaConsoleError 'There was no manifest file found inside the package'; + Remove-Item -Path $DownloadDirectory -Recurse -Force; + return; + } + } + } elseif ([IO.Path]::GetExtension($FileName) -eq '.msi') { + + <# + Handles installation of Icinga Agent MSI Packages + #> + + $IcingaData = Get-IcingaAgentInstallation; + $InstalledVersion = Get-IcingaAgentVersion; + $InstallTarget = $IcingaData.RootDir; + $InstallDir = Get-IcingaPowerShellConfig -Path 'Framework.Icinga.AgentLocation'; + $ConfigUser = Get-IcingaPowerShellConfig -Path 'Framework.Icinga.ServiceUser'; + $ServiceUser = $IcingaData.User; + + if ([string]::IsNullOrEmpty($InstallDir) -eq $FALSE) { + if ((Test-Path $InstallDir) -eq $FALSE) { + [void](New-Item -Path $InstallDir -ItemType Directory -Force); + } + $InstallTarget = $InstallDir; + } + + if ([string]::IsNullOrEmpty($ConfigUser) -eq $FALSE) { + $ServiceUser = $ConfigUser; + } + + [string]$InstallFolderMsg = $InstallTarget; + + if ([string]::IsNullOrEmpty($InstallTarget) -eq $FALSE) { + $InstallTarget = [string]::Format(' INSTALL_ROOT="{0}"', $InstallTarget); + } else { + $InstallTarget = ''; + if ($IcingaData.Architecture -eq 'x86') { + $InstallFolderMsg = Join-Path -Path ${env:ProgramFiles(x86)} -ChildPath 'icinga2'; + } else { + $InstallFolderMsg = Join-Path -Path $env:ProgramFiles -ChildPath 'icinga2'; + } + } + + $MSIData = & powershell.exe -Command { Use-Icinga; return Read-IcingaMSIMetadata -File $args[0] } -Args $DownloadDestination; + + if ($InstalledVersion.Full -eq $MSIData.ProductVersion -And $Force -eq $FALSE) { + Write-IcingaConsoleError 'The package "agent" with version "{0}" is already installed. Use "-Force" to re-install the component' -Objects $InstalledVersion.Full; + Remove-Item -Path $DownloadDirectory -Recurse -Force; + + return; + } + + Write-IcingaConsoleNotice 'Installing component "agent" with version "{0}" into "{1}"' -Objects $MSIData.ProductVersion, $InstallFolderMsg; + + if ($IcingaData.Installed) { + if ((Uninstall-IcingaAgent) -eq $FALSE) { + return; + } + } + + $InstallProcess = powershell.exe -Command { + $IcingaInstaller = $args[0]; + $InstallTarget = $args[1]; + Use-Icinga; + + $InstallProcess = Start-IcingaProcess -Executable 'MsiExec.exe' -Arguments ([string]::Format('/quiet /i "{0}" {1}', $IcingaInstaller, $InstallTarget)) -FlushNewLines; + + return $InstallProcess; + } -Args $DownloadDestination, $InstallTarget; + + if ($InstallProcess.ExitCode -ne 0) { + Write-IcingaConsoleError -Message 'Failed to install component "agent": {0}{1}' -Objects $InstallProcess.Message, $InstallProcess.Error; + return $FALSE; + } + + Set-IcingaAgentServiceUser -User $ServiceUser -SetPermission; + + Write-IcingaConsoleNotice 'Installation of component "agent" with version "{0}" was successful.' -Objects $MSIData.ProductVersion; + } else { + Write-IcingaConsoleError ([string]::Format('Unsupported file extension "{0}" found for package "{1}". Aborting installation', ([IO.Path]::GetExtension($FileName)), $Name.ToLower())); + } + + Start-Sleep -Seconds 1; + Remove-Item -Path $DownloadDirectory -Recurse -Force; +} diff --git a/lib/core/repository/Lock-IcingaComponent.psm1 b/lib/core/repository/Lock-IcingaComponent.psm1 new file mode 100644 index 00000000..ebf23628 --- /dev/null +++ b/lib/core/repository/Lock-IcingaComponent.psm1 @@ -0,0 +1,51 @@ +function Lock-IcingaComponent() +{ + param ( + [string]$Name = $null, + [string]$Version = $null + ); + + if ([string]::IsNullOrEmpty($Name)) { + Write-IcingaConsoleError 'You have to specify the component to lock'; + return; + } + + $Name = $Name.ToLower(); + if ([string]::IsNullOrEmpty($Version)) { + if ($Name -eq 'agent') { + $Version = (Get-IcingaAgentVersion).Full; + } else { + $ModuleData = Get-Module -ListAvailable -Name ([string]::Format('icinga-powershell-{0}', $Name)) -ErrorAction SilentlyContinue; + + if ($null -eq $ModuleData) { + $ModuleData = Get-Module -ListAvailable -Name "*$Name*" -ErrorAction SilentlyContinue; + } + + if ($null -ne $ModuleData) { + $Version = $ModuleData.Version.ToString(); + $Name = (Read-IcingaPackageManifest -File $ModuleData.Path).ComponentName; + } + } + } + + if ([string]::IsNullOrEmpty($Version)) { + Write-IcingaConsoleError 'Pinning the current version of component "{0}" is not possible, as it seems to be not installed. Please install the component first or manually specify version with "-Version"'; + return; + } + + $LockedComponents = Get-IcingaPowerShellConfig -Path 'Framework.Repository.ComponentLock'; + + if ($null -eq $LockedComponents) { + $LockedComponents = New-Object -TypeName PSObject; + } + + if (Test-IcingaPowerShellConfigItem -ConfigObject $LockedComponents -ConfigKey $Name) { + $LockedComponents.$Name = $Version; + } else { + $LockedComponents | Add-Member -MemberType NoteProperty -Name $Name -Value $Version; + } + + Write-IcingaConsoleNotice 'Locking of component "{0}" to version "{1}" successful. You can release the lock with "Unlock-IcingaComponent -Name {2}{0}{2}"' -Objects $Name, $Version, "'"; + + Set-IcingaPowerShellConfig -Path 'Framework.Repository.ComponentLock' -Value $LockedComponents; +} diff --git a/lib/core/repository/New-IcingaRepository.psm1 b/lib/core/repository/New-IcingaRepository.psm1 new file mode 100644 index 00000000..d4466b34 --- /dev/null +++ b/lib/core/repository/New-IcingaRepository.psm1 @@ -0,0 +1,54 @@ +function New-IcingaRepository() +{ + param ( + [string]$Name = $null, + [string]$Path = $null, + [string]$RemotePath = $null, + [switch]$Force = $FALSE + ); + + if ([string]::IsNullOrEmpty($Name)) { + Write-IcingaConsoleError 'You have to provide a name for the repository'; + return; + } + + if ([string]::IsNullOrEmpty($Path) -Or (Test-Path $Path) -eq $FALSE) { + Write-IcingaConsoleError 'The provided path "{0}" does not exist' -Objects $Path; + return; + } + + if ([string]::IsNullOrEmpty($RemotePath)) { + Write-IcingaConsoleWarning 'No explicit remote path has been defined. Using local path "{0}" as remote path' -Objects $Path; + $RemotePath = $Path; + } + + $CurrentRepositories = Get-IcingaPowerShellConfig -Path 'Framework.Repository.Repositories'; + + if ($null -eq $CurrentRepositories) { + $CurrentRepositories = New-Object -TypeName PSObject; + } + + if (Test-IcingaPowerShellConfigItem -ConfigObject $CurrentRepositories -ConfigKey $Name) { + Write-IcingaConsoleError 'A repository with the given name "{0}" does already exist. Use "Update-IcingaRepository -Name {1}{0}{1}" to update it.' -Objects $Name, "'"; + return; + } + + $IcingaRepository = New-IcingaRepositoryFile -Path $Path -RemotePath $RemotePath; + + [array]$ConfigCount = $IcingaRepository.Packages.PSObject.Properties.Count; + + if ($ConfigCount.Count -eq 0) { + Write-IcingaConsoleWarning 'Created empty repository at location "{0}"' -Objects $Path; + } + [array]$RepoCount = $CurrentRepositories.PSObject.Properties.Count; + + $CurrentRepositories | Add-Member -MemberType NoteProperty -Name $Name -Value (New-Object -TypeName PSObject); + $CurrentRepositories.$Name | Add-Member -MemberType NoteProperty -Name 'LocalPath' -Value $Path; + $CurrentRepositories.$Name | Add-Member -MemberType NoteProperty -Name 'RemotePath' -Value $RemotePath; + $CurrentRepositories.$Name | Add-Member -MemberType NoteProperty -Name 'CloneSource' -Value $null; + $CurrentRepositories.$Name | Add-Member -MemberType NoteProperty -Name 'UseSCP' -Value $FALSE; + $CurrentRepositories.$Name | Add-Member -MemberType NoteProperty -Name 'Order' -Value $RepoCount.Count; + $CurrentRepositories.$Name | Add-Member -MemberType NoteProperty -Name 'Enabled' -Value $True; + + Set-IcingaPowerShellConfig -Path 'Framework.Repository.Repositories' -Value $CurrentRepositories; +} diff --git a/lib/core/repository/New-IcingaRepositoryFile.psm1 b/lib/core/repository/New-IcingaRepositoryFile.psm1 new file mode 100644 index 00000000..11320f81 --- /dev/null +++ b/lib/core/repository/New-IcingaRepositoryFile.psm1 @@ -0,0 +1,91 @@ +function New-IcingaRepositoryFile() +{ + param ( + [string]$Path = $null, + [string]$RemotePath = $null + ); + + $RepoFile = 'ifw.repo.json'; + $RepoPath = Join-Path -Path $Path -ChildPath $RepoFile; + + $IcingaRepository = New-Object -TypeName PSObject; + $IcingaRepository | Add-Member -MemberType NoteProperty -Name 'Info' -Value (New-Object -TypeName PSObject); + + # Info + $IcingaRepository.Info | Add-Member -MemberType NoteProperty -Name 'LocalSource' -Value $Path; + $IcingaRepository.Info | Add-Member -MemberType NoteProperty -Name 'RemoteSource' -Value $RemotePath; + $IcingaRepository.Info | Add-Member -MemberType NoteProperty -Name 'Created' -Value ((Get-Date).ToUniversalTime().ToString('yyyy\/MM\/dd HH:mm:ss')); + $IcingaRepository.Info | Add-Member -MemberType NoteProperty -Name 'Updated' -Value $IcingaRepository.Info.Created; + $IcingaRepository.Info | Add-Member -MemberType NoteProperty -Name 'RepoHash' -Value $null; + + # Packages + $IcingaRepository | Add-Member -MemberType NoteProperty -Name 'Packages' -Value (New-Object -TypeName PSObject); + + $RepositoryFolder = Get-ChildItem -Path $Path -Recurse -Include '*.msi', '*.zip'; + + foreach ($entry in $RepositoryFolder) { + $RepoFilePath = $entry.FullName.Replace($Path, ''); + $FileHash = Get-FileHash -Path $entry.FullName -Algorithm SHA256; + $ComponentName = ''; + + $IcingaForWindowsPackage = New-Object -TypeName PSObject; + $IcingaForWindowsPackage | Add-Member -MemberType NoteProperty -Name 'Hash' -Value $FileHash.Hash; + $IcingaForWindowsPackage | Add-Member -MemberType NoteProperty -Name 'Location' -Value $RepoFilePath; + $IcingaForWindowsPackage | Add-Member -MemberType NoteProperty -Name 'RelativePath' -Value $TRUE; + + if ([IO.Path]::GetExtension($entry.Name) -eq '.zip') { + $IcingaPackage = Read-IcingaPackageManifest -File $entry.FullName; + $IcingaService = $null; + $Version = $null; + + if ($null -ne $IcingaPackage) { + $PackageVersion = $IcingaPackage.ModuleVersion; + $ComponentName = $IcingaPackage.ComponentName; + } else { + $IcingaService = Read-IcingaServicePackage -File $entry.FullName; + } + if ($null -ne $IcingaService) { + $PackageVersion = $IcingaService.ProductVersion; + $ComponentName = $IcingaService.ComponentName; + } + + [bool]$IsSnapshot = $FALSE; + + if ($entry.FullName.ToLower() -like '*\master.zip') { + $IsSnapshot = $TRUE; + } + + if ([string]::IsNullOrEmpty($ComponentName) -eq $FALSE) { + $IcingaForWindowsPackage | Add-Member -MemberType NoteProperty -Name 'Version' -Value $PackageVersion; + $IcingaForWindowsPackage | Add-Member -MemberType NoteProperty -Name 'Snapshot' -Value $IsSnapshot; + $IcingaForWindowsPackage | Add-Member -MemberType NoteProperty -Name 'Architecture' -Value 'Multi'; + } + } elseif ([IO.Path]::GetExtension($entry.Name) -eq '.msi') { + $IcingaPackage = Read-IcingaMSIMetadata -File $entry.FullName; + + if ([string]::IsNullOrEmpty($IcingaPackage.ProductName) -eq $FALSE -And $IcingaPackage.ProductName -eq 'Icinga 2') { + $IcingaForWindowsPackage | Add-Member -MemberType NoteProperty -Name 'Version' -Value $IcingaPackage.ProductVersion; + $IcingaForWindowsPackage | Add-Member -MemberType NoteProperty -Name 'Snapshot' -Value $IcingaPackage.Snapshot; + $IcingaForWindowsPackage | Add-Member -MemberType NoteProperty -Name 'Architecture' -Value $IcingaPackage.Architecture; + $ComponentName = 'agent'; + } + } + + if ([string]::IsNullOrEmpty($ComponentName)) { + continue; + } + + if (Test-IcingaPowerShellConfigItem -ConfigObject $IcingaRepository.Packages -ConfigKey $ComponentName) { + $IcingaRepository.Packages.$ComponentName += $IcingaForWindowsPackage; + } else { + $IcingaRepository.Packages | Add-Member -MemberType NoteProperty -Name $ComponentName -Value @(); + $IcingaRepository.Packages.$ComponentName += $IcingaForWindowsPackage; + } + + $IcingaRepository.Info.RepoHash = Get-IcingaRepositoryHash -Path $Path; + } + + Set-Content -Path $RepoPath -Value (ConvertTo-Json -InputObject $IcingaRepository -Depth 100); + + return $IcingaRepository; +} diff --git a/lib/core/repository/Pop-IcingaRepository.psm1 b/lib/core/repository/Pop-IcingaRepository.psm1 new file mode 100644 index 00000000..da96d831 --- /dev/null +++ b/lib/core/repository/Pop-IcingaRepository.psm1 @@ -0,0 +1,36 @@ +function Pop-IcingaRepository() +{ + param ( + [string]$Name = $null + ); + + if ([string]::IsNullOrEmpty($Name)) { + Write-IcingaConsoleError 'You have to provide a name for the repository'; + return; + } + + $CurrentRepositories = Get-IcingaPowerShellConfig -Path 'Framework.Repository.Repositories'; + + if ($null -eq $CurrentRepositories) { + Write-IcingaConsoleNotice 'You have no repositories configured yet.'; + return; + } + + [array]$RepoList = Get-IcingaRepositories; + [int]$Index = $RepoList.Count - 1; + + foreach ($repo in $RepoList) { + if ($repo.Name -eq $Name) { + continue; + } + + $CurrentRepositories.($repo.Name).Order = [int]$Index; + $Index -= 1; + } + + $CurrentRepositories.$Name.Order = [int]$Index; + + Write-IcingaConsoleNotice 'The repository "{0}" was put at the bottom of the repository list' -Objects $Name; + + Set-IcingaPowerShellConfig -Path 'Framework.Repository.Repositories' -Value $CurrentRepositories; +} diff --git a/lib/core/repository/Push-IcingaRepository.psm1 b/lib/core/repository/Push-IcingaRepository.psm1 new file mode 100644 index 00000000..225a017f --- /dev/null +++ b/lib/core/repository/Push-IcingaRepository.psm1 @@ -0,0 +1,43 @@ +function Push-IcingaRepository() +{ + param ( + [string]$Name = $null, + [switch]$Silent = $FALSE + ); + + if ([string]::IsNullOrEmpty($Name)) { + if ($Silent -eq $FALSE) { + Write-IcingaConsoleError 'You have to provide a name for the repository'; + } + return; + } + + $CurrentRepositories = Get-IcingaPowerShellConfig -Path 'Framework.Repository.Repositories'; + + if ($null -eq $CurrentRepositories) { + if ($Silent -eq $FALSE) { + Write-IcingaConsoleNotice 'You have no repositories configured yet.'; + } + return; + } + + [array]$RepoList = Get-IcingaRepositories; + [int]$Index = 0; + + foreach ($repo in $RepoList) { + if ($repo.Name -eq $Name) { + continue; + } + + $CurrentRepositories.($repo.Name).Order = [int]$Index; + $Index += 1; + } + + $CurrentRepositories.$Name.Order = [int]$Index; + + if ($Silent -eq $FALSE) { + Write-IcingaConsoleNotice 'The repository "{0}" was put at the top of the repository list' -Objects $Name; + } + + Set-IcingaPowerShellConfig -Path 'Framework.Repository.Repositories' -Value $CurrentRepositories; +} diff --git a/lib/core/repository/Read-IcingaMSIMetadata.psm1 b/lib/core/repository/Read-IcingaMSIMetadata.psm1 new file mode 100644 index 00000000..508d313c --- /dev/null +++ b/lib/core/repository/Read-IcingaMSIMetadata.psm1 @@ -0,0 +1,77 @@ +function Read-IcingaMSIMetadata() +{ + param ( + [string]$File = $null + ); + + if ([string]::IsNullOrEmpty($File) -Or (Test-Path $File) -eq $FALSE) { + Write-IcingaConsoleError 'The provided file "{0}" does not exist' -Objects $File; + return $null; + } + + if ([IO.Path]::GetExtension($File) -ne '.msi') { + Write-IcingaConsoleError 'This Cmdlet is only supporting files with .msi extension. Extension "{0}" given.' -Objects ([IO.Path]::GetExtension($File)); + return $null; + } + + $AgentFile = Get-Item $File; + $MSIPackageData = @{ + 'ProductCode' = ''; + 'ProductVersion' = ''; + 'ProductName' = ''; + } + + [array]$MSIObjects = $MSIPackageData.Keys; + + try { + $InstallerInstance = New-Object -ComObject 'WindowsInstaller.Installer'; + $MSIPackage = $InstallerInstance.OpenDatabase($File, 0); + + foreach ($PackageInfo in $MSIObjects) { + $MSIQuery = [string]::Format( + "SELECT `Value` FROM `Property` WHERE `Property` = '{0}'", + $PackageInfo + ); + $MSIDb = $MSIPackage.OpenView($MSIQuery); + + if ($null -eq $MSIDb) { + continue; + } + + $MSIDb.Execute(); + $MSITable = $MSIDb.Fetch(); + + if ($null -eq $MSITable) { + continue; + } + + $MSIPackageData[$PackageInfo] = $MSITable.GetType().InvokeMember('StringData', 'GetProperty', $null, $MSITable, 1); + + $MSIDb.Close(); + $MSIPackage.Commit(); + } + + $MSIPackage.Commit(); + $MSIPackage = $null; + [void]([System.Runtime.InteropServices.Marshal]::ReleaseComObject($InstallerInstance)); + + if ($AgentFile.Name.Contains('x86_64')) { + $MSIPackageData.Add('Architecture', 'x64') + } else { + $MSIPackageData.Add('Architecture', 'x86') + } + + [Version]$PackageVersion = $MSIPackageData.ProductVersion; + if ($PackageVersion.Revision -eq -1) { + $MSIPackageData.Add('Snapshot', $False); + } else { + $MSIPackageData.Add('Snapshot', $True); + } + + return $MSIPackageData; + } catch { + Write-IcingaConsoleError 'Failed to query MSI package information for package "{0}". Exception: {1}' -Objects $File, $_.Exception.Message; + } + + return $null; +} diff --git a/lib/core/repository/Read-IcingaPackageManifest.psm1 b/lib/core/repository/Read-IcingaPackageManifest.psm1 new file mode 100644 index 00000000..78214c6f --- /dev/null +++ b/lib/core/repository/Read-IcingaPackageManifest.psm1 @@ -0,0 +1,96 @@ +function Read-IcingaPackageManifest() +{ + param ( + [string]$File = $null + ); + + if ([string]::IsNullOrEmpty($File) -Or (Test-Path $File) -eq $FALSE) { + Write-IcingaConsoleError 'The provided file "{0}" does not exist' -Objects $File; + return $null; + } + + if ((Test-IcingaAddTypeExist 'System.IO.Compression.FileSystem') -eq $FALSE) { + Add-Type -Assembly 'System.IO.Compression.FileSystem'; + } + + if ([IO.Path]::GetExtension($File) -ne '.zip' -And [IO.Path]::GetExtension($File) -ne '.psd1') { + Write-IcingaConsoleError 'Your Icinga for Windows manifest must be inside a .zip file or directly given on the "-File" argument. Extension "{0}" given.' -Objects ([IO.Path]::GetExtension($File)); + return $null; + } + + try { + $ZipPackage = $null; + + if ([IO.Path]::GetExtension($File) -eq '.zip') { + $ZipPackage = [System.IO.Compression.ZipFile]::OpenRead($File); + $PackageManifest = $null; + $FileName = $null; + + foreach ($entry in $ZipPackage.Entries) { + if ([IO.Path]::GetExtension($entry.FullName) -ne '.psd1') { + continue; + } + + $FileName = $entry.Name.Replace('.psd1', ''); + $FilePath = $entry.FullName.Replace($entry.Name, ''); + $FileStream = $entry.Open(); + $FileReader = [System.IO.StreamReader]::new($FileStream); + $PackageManifestContent = $FileReader.ReadToEnd(); + $FileReader.Dispose(); + + [ScriptBlock]$PackageScript = [ScriptBlock]::Create('return ' + $PackageManifestContent); + $PackageManifest = (& $PackageScript); + + if ($null -eq $PackageManifest -Or $PackageManifest.Count -eq 0) { + continue; + } + + if ($PackageManifest.ContainsKey('PrivateData') -eq $FALSE -Or $PackageManifest.ContainsKey('ModuleVersion') -eq $FALSE) { + continue; + } + + break; + } + + $ZipPackage.Dispose(); + } elseif ([IO.Path]::GetExtension($File) -eq '.psd1') { + $FileName = (Get-Item -Path $File).Name.Replace('.psd1', ''); + $PackageManifestContent = Get-Content -Path $File -Raw; + [ScriptBlock]$PackageScript = [ScriptBlock]::Create('return ' + $PackageManifestContent); + $PackageManifest = (& $PackageScript); + } else { + return $null; + } + + if ($null -eq $PackageManifest) { + return $null; + } + + $PackageManifest.Add('ComponentName', ''); + + if ([string]::IsNullOrEmpty($FileName) -eq $FALSE) { + if ($FileName.Contains('icinga-powershell-*')) { + $PackageManifest.ComponentName = $FileName.Replace('icinga-powershell-', ''); + } else { + if ($PackageManifest.ContainsKey('PrivateData') -And $PackageManifest.PrivateData.ContainsKey('Name') -And $PackageManifest.PrivateData.ContainsKey('Type')) { + if ($PackageManifest.PrivateData.Name -eq 'Icinga for Windows' -And $PackageManifest.PrivateData.Type -eq 'framework') { + $PackageManifest.ComponentName = 'framework'; + } else { + $PackageManifest.ComponentName = ($PackageManifest.PrivateData.Name -Replace 'Windows' -Replace '\W').ToLower(); + } + } + } + } + + return $PackageManifest; + } catch { + $ExMsg = $_.Exception.Message; + Write-IcingaConsoleError 'Failed to read package content and/or manifest file: {0}' -Objects $ExMsg; + } finally { + if ($null -ne $ZipPackage) { + $ZipPackage.Dispose(); + } + } + + return $null; +} diff --git a/lib/core/repository/Read-IcingaRepositoryFile.psm1 b/lib/core/repository/Read-IcingaRepositoryFile.psm1 new file mode 100644 index 00000000..0d6ea6ef --- /dev/null +++ b/lib/core/repository/Read-IcingaRepositoryFile.psm1 @@ -0,0 +1,66 @@ +function Read-IcingaRepositoryFile() +{ + param ( + [string]$Name = $null + ); + + if ([string]::IsNullOrEmpty($Name)) { + Write-IcingaConsoleError 'You have to provide a name for the repository'; + return $null; + } + + $Repository = Get-IcingaPowerShellConfig -Path ([string]::Format('Framework.Repository.Repositories.{0}', $Name)); + + if ($null -eq $Repository) { + Write-IcingaConsoleError 'A repository with the given name "{0}" does not exist. Use "New-IcingaRepository" or "Sync-IcingaForWindowsRepository" to create a new one.' -Objects $Name; + return $null; + } + + $RepoPath = $null; + $Content = $null; + + if ([string]::IsNullOrEmpty($Repository.LocalPath) -eq $FALSE -And (Test-Path -Path $Repository.LocalPath)) { + $RepoPath = $Repository.LocalPath; + $Content = Get-Content -Path (Join-Path -Path $RepoPath -ChildPath 'ifw.repo.json') -Raw; + } elseif ([string]::IsNullOrEmpty($Repository.RemotePath) -eq $FALSE -And (Test-Path -Path $Repository.RemotePath)) { + $RepoPath = $Repository.RemotePath; + $WebContent = Get-Content -Path (Join-Path -Path $RepoPath -ChildPath 'ifw.repo.json') -Raw; + } else { + try { + $WebContent = Invoke-WebRequest -UseBasicParsing -Uri $Repository.RemotePath; + $RepoPath = $Repository.RemotePath; + } catch { + # Nothing to do + } + + if ($null -eq $WebContent) { + try { + $WebContent = Invoke-WebRequest -UseBasicParsing -Uri (Join-WebPath -Path $Repository.RemotePath -ChildPath 'ifw.repo.json'); + } catch { + Write-IcingaConsoleError 'Failed to read repository file from "{0}" or "{0}/ifw.repo.json". Exception: {1}' -Objects $Repository.RemotePath, $_.Exception.Message; + return $null; + } + $RepoPath = $Repository.RemotePath; + } + + if ($null -eq $WebContent) { + Write-IcingaConsoleError 'Unable to fetch data for repository "{0}" from any configured location' -Objects $Name; + return $null; + } + + if ($WebContent.RawContent.Contains('application/octet-stream')) { + $Content = [System.Text.Encoding]::UTF8.GetString($WebContent.Content) + } else { + $Content = $WebContent.Content; + } + } + + if ($null -eq $Content) { + Write-IcingaConsoleError 'Unable to fetch data for repository "{0}" from any configured location' -Objects $Name; + return $null; + } + + $RepositoryObject = ConvertFrom-Json -InputObject $Content; + + return $RepositoryObject; +} diff --git a/lib/core/repository/Read-IcingaServicePackage.psm1 b/lib/core/repository/Read-IcingaServicePackage.psm1 new file mode 100644 index 00000000..cd0f413a --- /dev/null +++ b/lib/core/repository/Read-IcingaServicePackage.psm1 @@ -0,0 +1,90 @@ +function Read-IcingaServicePackage() +{ + param ( + [string]$File = $null + ); + + if ([string]::IsNullOrEmpty($File) -Or (Test-Path $File) -eq $FALSE) { + Write-IcingaConsoleError 'The provided file "{0}" does not exist' -Objects $File; + return $null; + } + + if ((Test-IcingaAddTypeExist 'System.IO.Compression.FileSystem') -eq $FALSE) { + Add-Type -Assembly 'System.IO.Compression.FileSystem'; + } + + if ([IO.Path]::GetExtension($File) -ne '.zip' -And [IO.Path]::GetExtension($File) -ne '.exe') { + Write-IcingaConsoleError 'Your service binary must be inside a .zip file or directly given on the "-File" argument. Extension "{0}" given.' -Objects ([IO.Path]::GetExtension($File)); + return $null; + } + + [hashtable]$BinaryData = @{ + 'CompanyName' = ''; + 'FileVersion' = ''; + 'ProductVersion' = ''; + 'ComponentName' = 'service'; + } + + try { + $ZipPackage = $null; + + if ([IO.Path]::GetExtension($File) -eq '.zip') { + $ZipPackage = [System.IO.Compression.ZipFile]::OpenRead($File); + + foreach ($entry in $ZipPackage.Entries) { + if ([IO.Path]::GetExtension($entry.FullName) -ne '.exe') { + continue; + } + + $ServiceTempDir = New-IcingaTemporaryDirectory; + $BinaryFile = (Join-Path -Path $ServiceTempDir -ChildPath $entry.Name); + [System.IO.Compression.ZipFileExtensions]::ExtractToFile( + $entry, + (Join-Path -Path $ServiceTempDir -ChildPath $entry.Name), + $TRUE + ); + + $ServiceBin = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($BinaryFile); + + if ($ServiceBin.CompanyName -ne 'Icinga GmbH') { + Remove-Item -Path $ServiceTempDir -Recurse -Force; + continue; + } + + $BinaryData.CompanyName = $ServiceBin.CompanyName; + $BinaryData.ProductVersion = ([version]($ServiceBin.ProductVersion)).ToString(3); + $BinaryData.FileVersion = ([version]($ServiceBin.FileVersion)).ToString(3); + break; + } + + $ZipPackage.Dispose(); + } elseif ([IO.Path]::GetExtension($File) -eq '.exe') { + $ServiceBin = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($File); + + if ($ServiceBin.CompanyName -ne 'Icinga GmbH') { + return $null; + } + + $BinaryData.CompanyName = $ServiceBin.CompanyName; + $BinaryData.ProductVersion = ([version]($ServiceBin.ProductVersion)).ToString(3); + $BinaryData.FileVersion = ([version]($ServiceBin.FileVersion)).ToString(3); + } else { + return $null; + } + + if ([string]::IsNullOrEmpty($BinaryData.ProductVersion)) { + return $null; + } + + return $BinaryData; + } catch { + $ExMsg = $_.Exception.Message; + Write-IcingaConsoleError 'Failed to read package content and/or binary file: {0}' -Objects $ExMsg; + } finally { + if ($null -ne $ZipPackage) { + $ZipPackage.Dispose(); + } + } + + return $null; +} diff --git a/lib/core/repository/Remove-IcingaRepository.psm1 b/lib/core/repository/Remove-IcingaRepository.psm1 new file mode 100644 index 00000000..d43d553f --- /dev/null +++ b/lib/core/repository/Remove-IcingaRepository.psm1 @@ -0,0 +1,29 @@ +function Remove-IcingaRepository() +{ + param ( + [string]$Name = $null + ); + + if ([string]::IsNullOrEmpty($Name)) { + Write-IcingaConsoleError 'You have to provide a name for the repository to remove'; + return; + } + + $CurrentRepositories = Get-IcingaPowerShellConfig -Path 'Framework.Repository.Repositories'; + + if ((Test-IcingaPowerShellConfigItem -ConfigObject $CurrentRepositories -ConfigKey $Name) -eq $FALSE) { + Write-IcingaConsoleError 'A repository with the name "{0}" is not configured' -Objects $Name; + return; + } + + Push-IcingaRepository -Name $Name -Silent; + + Remove-IcingaPowerShellConfig -Path ( + [string]::Format( + 'Framework.Repository.Repositories.{0}', + $Name + ) + ); + + Write-IcingaConsoleNotice 'The repository with the name "{0}" was successfully removed' -Objects $Name; +} diff --git a/lib/core/repository/Search-IcingaRepository.psm1 b/lib/core/repository/Search-IcingaRepository.psm1 new file mode 100644 index 00000000..4c13c2be --- /dev/null +++ b/lib/core/repository/Search-IcingaRepository.psm1 @@ -0,0 +1,126 @@ +function Search-IcingaRepository() +{ + param ( + [string]$Name = $null, + [string]$Version = $null, + [switch]$Release = $FALSE, + [switch]$Snapshot = $FALSE + ); + + if ($Version -eq 'release') { + $Version = $null; + } + + if ([string]::IsNullOrEmpty($Name)) { + Write-IcingaConsoleError 'You have to provide a component name'; + return; + } + + if ($Release -eq $FALSE -And $Snapshot -eq $FALSE) { + $Release = $TRUE; + } + + $Repositories = Get-IcingaRepositories -ExcludeDisabled; + [Version]$LatestVersion = $null; + [string]$SourcePath = $null; + [bool]$FoundPackage = $FALSE; + [array]$Output = @(); + [bool]$FoundPackage = $FALSE; + + $SearchList = New-Object -TypeName PSObject; + $SearchList | Add-Member -MemberType NoteProperty -Name 'Repos' -Value @(); + + foreach ($entry in $Repositories) { + $RepoContent = Read-IcingaRepositoryFile -Name $entry.Name; + + if ($null -eq $RepoContent) { + continue; + } + + if ((Test-IcingaPowerShellConfigItem -ConfigObject $RepoContent -ConfigKey 'Packages') -eq $FALSE) { + continue; + } + + foreach ($repoEntry in $RepoContent.Packages.PSObject.Properties.Name) { + + if ($repoEntry -NotLike $Name) { + continue; + } + + $RepoData = New-Object -TypeName PSObject; + $RepoData | Add-Member -MemberType NoteProperty -Name 'Name' -Value $entry.Name; + $RepoData | Add-Member -MemberType NoteProperty -Name 'RemoteSource' -Value $RepoContent.Info.RemoteSource; + $RepoData | Add-Member -MemberType NoteProperty -Name 'ComponentName' -Value $repoEntry; + $RepoData | Add-Member -MemberType NoteProperty -Name 'Packages' -Value @(); + + foreach ($package in $RepoContent.Packages.$repoEntry) { + + $ComponentData = New-Object -TypeName PSObject; + $ComponentData | Add-Member -MemberType NoteProperty -Name 'Version' -Value $package.Version; + $ComponentData | Add-Member -MemberType NoteProperty -Name 'Location' -Value $package.Location; + $ComponentData | Add-Member -MemberType NoteProperty -Name 'Snapshot' -Value $package.Snapshot; + + if ($Snapshot -And $package.Snapshot -eq $TRUE -And [string]::IsNullOrEmpty($Version)) { + $RepoData.Packages += $ComponentData; + continue; + } + + if ($Snapshot -And $package.Snapshot -eq $TRUE -And [string]::IsNullOrEmpty($Version) -eq $FALSE -And [version]$package.Version -eq [version]$Version) { + $RepoData.Packages += $ComponentData; + continue; + } + + if ($Release -And [string]::IsNullOrEmpty($Version) -And $package.Snapshot -eq $FALSE) { + $RepoData.Packages += $ComponentData; + continue; + } + + if ([string]::IsNullOrEmpty($Version) -eq $FALSE -And $Release -eq $FALSE -And $Snapshot -eq $FALSE -And $package.Snapshot -eq $FALSE) { + $RepoData.Packages += $ComponentData; + continue; + } + } + + if ($RepoData.Packages.Count -ne 0) { + $FoundPackage = $TRUE; + } + + $SearchList.Repos += $RepoData; + } + } + + if ($FoundPackage -eq $FALSE) { + $SearchVersion = 'release'; + if ([string]::IsNullOrEmpty($Version) -eq $FALSE) { + $SearchVersion = $Version; + } + if ($Release) { + Write-IcingaConsoleNotice 'The component "{0}" was not found on stable channel with version "{1}"' -Objects $Name, $SearchVersion; + } + if ($Snapshot) { + Write-IcingaConsoleNotice 'The component "{0}" was not found on snapshot channel with version "{1}"' -Objects $Name, $SearchVersion; + } + return; + } + + foreach ($repo in $SearchList.Repos) { + if ($repo.Packages.Count -eq 0) { + continue; + } + + $Output += $repo.Name; + $Output += '-----------'; + $Output += [string]::Format('Source => {0}', $repo.RemoteSource); + $Output += ''; + $Output += $repo.ComponentName; + + [array]$VersionList = $repo.Packages | Sort-Object { $_.Version } -Descending; + + foreach ($componentData in $VersionList) { + $Output += [string]::Format('{0} => {1}', $componentData.Version, $componentData.Location); + } + $Output += ''; + } + + Write-Host ($Output | Out-String); +} diff --git a/lib/core/repository/Show-Icinga.psm1 b/lib/core/repository/Show-Icinga.psm1 new file mode 100644 index 00000000..cebee981 --- /dev/null +++ b/lib/core/repository/Show-Icinga.psm1 @@ -0,0 +1,56 @@ +function Show-Icinga() +{ + $IcingaInstallation = Get-IcingaInstallation -Release; + [array]$Output = @( 'Icinga for Windows environment' ); + [int]$MaxComponentLength = Get-IcingaMaxTextLength -TextArray $IcingaInstallation.Keys; + [int]$MaxVersionLength = Get-IcingaMaxTextLength -TextArray $IcingaInstallation.Values.CurrentVersion; + [string]$ComponentHeader = Add-IcingaWhiteSpaceToString -Text 'Component' -Length $MaxComponentLength; + [string]$ComponentLine = Add-IcingaWhiteSpaceToString -Text '---' -Length $MaxComponentLength; + $Output += '-----------'; + $Output += ''; + $Output += 'Installed components on this system'; + $Output += ''; + $Output += [string]::Format('{0} {1} Available', $ComponentHeader, ((Add-IcingaWhiteSpaceToString -Text 'Version' -Length $MaxVersionLength))); + $Output += [string]::Format('{0} {1} ---', $ComponentLine, ((Add-IcingaWhiteSpaceToString -Text '---' -Length $MaxVersionLength))); + + foreach ($component in $IcingaInstallation.Keys) { + $Data = $IcingaInstallation[$component]; + $LatestVersion = $Data.LatestVersion; + $CurrentVersion = $Data.CurrentVersion; + + if ([string]::IsNullOrEmpty($Data.LockedVersion) -eq $FALSE) { + if ($Data.LockedVersion -eq $Data.CurrentVersion) { + $CurrentVersion = [string]::Format('{0}*', $CurrentVersion); + } else { + $LatestVersion = [string]::Format('{0}*', $Data.LockedVersion); + } + } + + [string]$ComponentName = Add-IcingaWhiteSpaceToString -Text $component -Length $MaxComponentLength; + $Output += [string]::Format('{0} {1} {2}', $ComponentName, (Add-IcingaWhiteSpaceToString -Text $CurrentVersion -Length $MaxVersionLength), $LatestVersion); + } + + $Output += ''; + $Output += 'Available versions flagged with "*" mean that this component is locked to this version'; + + $IcingaForWindowsService = Get-IcingaForWindowsServiceData; + $IcingaAgentService = Get-IcingaAgentInstallation; + $WindowsInformation = Get-IcingaWindowsInformation Win32_OperatingSystem | Select-Object Version, BuildNumber, Caption; + + $Output += ''; + $Output += 'Environment configuration'; + $Output += ''; + $Output += ([string]::Format('PowerShell Root => {0}', (Get-IcingaForWindowsRootPath))); + $Output += ([string]::Format('Icinga for Windows Service Path => {0}', $IcingaForWindowsService.Directory)); + $Output += ([string]::Format('Icinga for Windows Service User => {0}', $IcingaForWindowsService.User)); + $Output += ([string]::Format('Icinga Agent Path => {0}', $IcingaAgentService.RootDir)); + $Output += ([string]::Format('Icinga Agent User => {0}', $IcingaAgentService.User)); + $Output += ([string]::Format('PowerShell Version => {0}', $PSVersionTable.PSVersion.ToString())); + $Output += ([string]::Format('Operating System => {0}', $WindowsInformation.Caption)); + $Output += ([string]::Format('Operating System Version => {0}', $WindowsInformation.Version)); + + $Output += ''; + $Output += (Show-IcingaRepository); + + Write-Output $Output; +} diff --git a/lib/core/repository/Show-IcingaRepository.psm1 b/lib/core/repository/Show-IcingaRepository.psm1 new file mode 100644 index 00000000..db2d859d --- /dev/null +++ b/lib/core/repository/Show-IcingaRepository.psm1 @@ -0,0 +1,32 @@ +function Show-IcingaRepository() +{ + [hashtable]$Repositories = @{ }; + [array]$RepoSummary = @( + 'List of configured repositories on this system. The list order matches the apply order.', + '' + ); + [array]$RepoList = Get-IcingaRepositories; + + foreach ($repo in $RepoList) { + + $RepoSummary += $repo.Name; + $RepoSummary += '-----------'; + + [int]$MaxLength = Get-IcingaMaxTextLength -TextArray $repo.Value.PSObject.Properties.Name; + [array]$RepoData = @(); + + foreach ($repoConfig in $repo.Value.PSObject.Properties) { + $PrintName = Add-IcingaWhiteSpaceToString -Text $repoConfig.Name -Length $MaxLength; + $RepoData += [string]::Format('{0} => {1}', $PrintName, $repoConfig.Value); + } + + $RepoSummary += $RepoData | Sort-Object; + $RepoSummary += ''; + } + + if ($RepoList.Count -eq 0) { + $RepoSummary += 'No repositories configured'; + } + + Write-Output $RepoSummary; +} diff --git a/lib/core/repository/Sync-IcingaRepository.psm1 b/lib/core/repository/Sync-IcingaRepository.psm1 new file mode 100644 index 00000000..dd9d4c73 --- /dev/null +++ b/lib/core/repository/Sync-IcingaRepository.psm1 @@ -0,0 +1,263 @@ +function Sync-IcingaRepository() +{ + param ( + [string]$Name = $null, + [string]$Path = $null, + [string]$RemotePath = $null, + [string]$Source = $null, + [switch]$UseSCP = $FALSE, + [switch]$Force = $FALSE, + [switch]$ForceTrust = $FALSE + ); + + if ([string]::IsNullOrEmpty($Name)) { + Write-IcingaConsoleError 'You have to provide a name for the repository'; + return; + } + + if ($UseSCP -And $null -eq (Get-Command 'scp' -ErrorAction SilentlyContinue) -And $null -eq (Get-Command 'ssh' -ErrorAction SilentlyContinue)) { + Write-IcingaConsoleWarning 'You cannot use SCP on this system, as SCP and/or SSH seem not to be installed'; + return; + } + + if ($UseSCP -And $Path.Contains(':') -eq $FALSE -And $Path.Contains('@') -eq $FALSE) { + Write-IcingaConsoleWarning 'You have to add host and username to your "-Path" argument. Example: "icinga@icinga.example.com:/var/www/icingarepo/" '; + return; + } + + if ([string]::IsNullOrEmpty($Path) -Or (Test-Path $Path) -eq $FALSE -And $UseSCP -eq $FALSE) { + Write-IcingaConsoleWarning 'The provided path "{0}" does not exist and will be created' -Objects $Path; + } + + if ([string]::IsNullOrEmpty($RemotePath)) { + Write-IcingaConsoleWarning 'No explicit remote path has been defined. Using local path "{0}" as remote path' -Objects $Path; + $RemotePath = $Path; + } + + if ([string]::IsNullOrEmpty($Source)) { + Write-IcingaConsoleError 'You have to specify a source to sync from'; + return; + } + + $CurrentRepositories = Get-IcingaPowerShellConfig -Path 'Framework.Repository.Repositories'; + + if ($null -eq $CurrentRepositories) { + $CurrentRepositories = New-Object -TypeName PSObject; + } + + if ((Test-IcingaPowerShellConfigItem -ConfigObject $CurrentRepositories -ConfigKey $Name) -And $Force -eq $FALSE) { + Write-IcingaConsoleError 'A repository with the given name "{0}" does already exist. Use "Update-IcingaRepository -Name {1}{0}{1}" to update it.' -Objects $Name, "'"; + return; + } + + if ((Test-Path $Path) -eq $FALSE -And $UseSCP -eq $FALSE) { + $FolderCreated = New-Item -Path $Path -ItemType Directory -Force -ErrorAction SilentlyContinue; + + if ($null -eq $FolderCreated) { + Write-IcingaConsoleError 'Unable to create repository folder at location "{0}". Please verify that you have permissions to write into the location and try again or create the folder manually' -Objects $Path; + return; + } + } + + $RepoFile = $null; + $SSHAuth = $null; + $RemovePath = $null; + + if (Test-Path $Source) { + $CopySource = Join-Path -Path $Source -ChildPath '\*'; + } else { + $CopySource = $Source; + } + + if ($UseSCP -eq $FALSE) { + $Path = Join-Path -Path $Path -ChildPath '\'; + $RemovePath = Join-Path -Path $Path -ChildPath '\*'; + } else { + $SSHIndex = $Path.IndexOf(':'); + $SSHAuth = $Path.Substring(0, $SSHIndex); + $Path = $Path.Substring($SSHIndex + 1, $Path.Length - $SSHIndex - 1); + + if ($Path[-1] -eq '/') { + $RemovePath = [string]::Format('{0}*', $Path); + } else { + $RemovePath = [string]::Format('{0}/*', $Path); + } + } + + # All cloning will be done into a local file first + $TmpDir = New-IcingaTemporaryDirectory; + $RepoFile = (Join-Path -Path $TmpDir -ChildPath 'ifw.repo.json'); + [bool]$HasNonRelative = $FALSE; + + if (Test-Path $CopySource) { # Sync source is local path + $Success = Copy-ItemSecure -Path $CopySource -Destination $TmpDir -Recurse -Force; + } else { # Sync Source is web path + $ProgressPreference = "SilentlyContinue"; + try { + Invoke-WebRequest -USeBasicParsing -Uri $Source -OutFile $RepoFile; + } catch { + try { + Invoke-WebRequest -USeBasicParsing -Uri (Join-WebPath -Path $Source -ChildPath 'ifw.repo.json') -OutFile $RepoFile; + } catch { + Write-IcingaConsoleError 'Unable to download repository file from "{0}". Exception: "{1}"' -Objects $Source, $_.Exception.Message; + $Success = Remove-Item -Path $TmpDir -Recurse -Force; + return; + } + } + + $RepoContent = Get-Content -Path $RepoFile -Raw; + $JsonRepo = ConvertFrom-Json -InputObject $RepoContent; + + foreach ($component in $JsonRepo.Packages.PSObject.Properties.Name) { + $IfWPackage = $JsonRepo.Packages.$component + + foreach ($package in $IfWPackage) { + $DownloadLink = $package.Location; + $TargetLocation = $TmpDir; + + if ($package.RelativePath -eq $TRUE) { + $DownloadLink = Join-WebPath -Path $JsonRepo.Info.RemoteSource -ChildPath $package.Location; + $TargetLocation = Join-Path -Path $TmpDir -ChildPath $package.Location; + + [void]( + New-Item ` + -ItemType Directory ` + -Path ( + $TargetLocation.SubString( + 0, + $TargetLocation.LastIndexOf('\') + ) + ) ` + -Force + ); + } else { + $HasNonRelative = $TRUE; + $FileName = $package.Location.Replace('/', '\'); + $Index = $FileName.LastIndexOf('\'); + $FileName = $FileName.SubString($Index, $FileName.Length - $Index); + $TargetLocation = Join-Path -Path $TmpDir -ChildPath $component; + [void](New-Item -ItemType Directory -Path $TargetLocation -Force); + $TargetLocation = Join-Path -Path $TargetLocation -ChildPath $FileName; + } + + try { + Write-IcingaConsoleNotice 'Syncing repository component "{0}" as file "{1}" into temp directory' -Objects $component, $package.Location; + Invoke-WebRequest -USeBasicParsing -Uri $DownloadLink -OutFile $TargetLocation; + } catch { + Write-IcingaConsoleError 'Failed to download repository component "{0}". Exception: "{1}"' -Objects $DownloadLink, $_.Exception.Message; + continue; + } + } + } + } + + [string]$CopySource = [string]::Format('{0}\*', $TmpDir); + + if ((Test-Path $RepoFile) -eq $FALSE) { + Write-IcingaConsoleError 'The files from this repository were cloned but no repository file was found. Deleting temporary files'; + $Success = Remove-Item -Path $TmpDir -Recurse -Force; + return; + } + + $RepoContent = Get-Content -Path $RepoFile -Raw; + $JsonRepo = ConvertFrom-Json -InputObject $RepoContent; + + if ($null -eq $JsonRepo) { + Write-IcingaConsoleError 'The repository file was found but it is either damaged or empty. Deleting temporary files'; + $Success = Remove-Item -Path $TmpDir -Recurse -Force; + return; + } + + $EnableRepo = $TRUE; + + if ($ForceTrust -eq $FALSE -And $UseSCP -eq $FALSE) { + if ($null -eq $JsonRepo.Info.RepoHash -Or [string]::IsNullOrEmpty($JsonRepo.Info.RepoHash)) { + Write-IcingaConsoleWarning 'The cloned repository file hash cannot be verified, as it is not present inside the repository file. The repository will be added, but disabled for security reasons. Review the content first and ensure you trust the source before enabling it.'; + $EnableRepo = $FALSE; + } elseif ($JsonRepo.Info.RepoHash -ne (Get-IcingaRepositoryHash -Path $TmpDir)) { + $Success = Remove-Item -Path $TmpDir -Recurse -Force; + Write-IcingaConsoleError 'The repository hash for the cloned repository is not matching the file hash of the files inside. Removing repository data'; + return; + } + } + + if ($HasNonRelative) { + [void](New-IcingaRepositoryFile -Path $TmpDir -RemotePath $RemotePath); + $RepoContent = Get-Content -Path $RepoFile -Raw; + $JsonRepo = ConvertFrom-Json -InputObject $RepoContent; + Start-Sleep -Seconds 2; + } + + $JsonRepo.Info.RepoHash = Get-IcingaRepositoryHash -Path $TmpDir; + $JsonRepo.Info.LocalSource = $Path; + $JsonRepo.Info.RemoteSource = $RemotePath; + $JsonRepo.Info.Updated = ((Get-Date).ToUniversalTime().ToString('yyyy\/MM\/dd HH:mm:ss')); + + Set-Content -Path $RepoFile -Value (ConvertTo-Json -InputObject $JsonRepo -Depth 100); + + if ($UseSCP -eq $FALSE) { # Windows target + $Success = Remove-Item -Path $RemovePath -Recurse -Force; + $Success = Copy-ItemSecure -Path $CopySource -Destination $Path -Recurse -Force; + + if ($Success -eq $FALSE) { + Write-IcingaConsoleError 'Unable to sync repository from location "{0}" to destination "{1}". Please verify that you have permissions to write into the location and try again or create the folder manually' -Objects $TmpDir, $Path; + $Success = Remove-Item -Path $TmpDir -Recurse -Force; + return; + } + } else { # Linux target + Write-IcingaConsoleNotice 'Creating directory over SSH for host and user "{0}" and path "{1}"' -Objects $SSHAuth, $Path; + + $Result = Start-IcingaProcess -Executable 'ssh' -Arguments ([string]::Format('{0} mkdir -p "{1}"', $SSHAuth, $Path)); + if ($Result.ExitCode -ne 0) { + # TODO: Add link to setup docs + Write-IcingaConsoleError 'SSH Error on directory creation: {0}' -Objects $Result.Error; + $Success = Remove-Item -Path $TmpDir -Recurse -Force; + return; + } + + Write-IcingaConsoleNotice 'Removing old repository files from "{0}"' -Objects $Path; + + $Result = Start-IcingaProcess -Executable 'ssh' -Arguments ([string]::Format('{0} rm -Rf "{1}"', $SSHAuth, $RemovePath)); + + if ($Result.ExitCode -ne 0) { + Write-IcingaConsoleError 'SSH Error on removing old repository data: {0}' -Objects $Result.Error; + $Success = Remove-Item -Path $TmpDir -Recurse -Force; + return; + } + + Write-IcingaConsoleNotice 'Syncing new repository files to "{0}"' -Objects $Path; + + $Result = Start-IcingaProcess -Executable 'scp' -Arguments ([string]::Format('-r "{0}" "{1}:{2}"', $CopySource, $SSHAuth, $Path)); + + if ($Result.ExitCode -ne 0) { + Write-IcingaConsoleError 'SCP Error while copying repository files: {0}' -Objects $Result.Error; + $Success = Remove-Item -Path $TmpDir -Recurse -Force; + return; + } + } + + $Success = Remove-Item -Path $TmpDir -Recurse -Force; + + if ((Test-IcingaPowerShellConfigItem -ConfigObject $CurrentRepositories -ConfigKey $Name) -And $Force -eq $TRUE) { + $CurrentRepositories.$Name.Enabled = $EnableRepo; + Set-IcingaPowerShellConfig -Path 'Framework.Repository.Repositories' -Value $CurrentRepositories; + Write-IcingaConsoleNotice 'Re-syncing of repository "{0}" was successful' -Objects $Name; + return; + } + + Write-IcingaConsoleNotice 'The repository was synced successfully. Use "Update-IcingaRepository" to sync possible changes from the source repository.'; + + [array]$RepoCount = $CurrentRepositories.PSObject.Properties.Count; + + $CurrentRepositories | Add-Member -MemberType NoteProperty -Name $Name -Value (New-Object -TypeName PSObject); + $CurrentRepositories.$Name | Add-Member -MemberType NoteProperty -Name 'LocalPath' -Value $Path; + $CurrentRepositories.$Name | Add-Member -MemberType NoteProperty -Name 'RemotePath' -Value $RemotePath; + $CurrentRepositories.$Name | Add-Member -MemberType NoteProperty -Name 'CloneSource' -Value $Source; + $CurrentRepositories.$Name | Add-Member -MemberType NoteProperty -Name 'UseSCP' -Value ([bool]$UseSCP); + $CurrentRepositories.$Name | Add-Member -MemberType NoteProperty -Name 'Order' -Value $RepoCount.Count; + $CurrentRepositories.$Name | Add-Member -MemberType NoteProperty -Name 'Enabled' -Value $EnableRepo; + + Set-IcingaPowerShellConfig -Path 'Framework.Repository.Repositories' -Value $CurrentRepositories; + + return; +} diff --git a/lib/core/repository/Uninstall-IcingaComponent.psm1 b/lib/core/repository/Uninstall-IcingaComponent.psm1 new file mode 100644 index 00000000..a4d8c8d3 --- /dev/null +++ b/lib/core/repository/Uninstall-IcingaComponent.psm1 @@ -0,0 +1,42 @@ +function Uninstall-IcingaComponent() +{ + param ( + [string]$Name = '', + [switch]$RemovePackageFiles = $FALSE + ); + + if ([string]::IsNullOrEmpty($Name)) { + Write-IcingaConsoleError 'You have to specify a component name to uninstall'; + return $FALSE; + } + + if ($Name.ToLower() -eq 'agent') { + return Uninstall-IcingaAgent -RemoveDataFolder:$RemovePackageFiles; + } + + if ($Name.ToLower() -eq 'service') { + return; Uninstall-IcingaForWindowsService -RemoveFiles:$RemovePackageFiles; + } + + $ModuleBase = Get-IcingaForWindowsRootPath; + $UninstallComponent = [string]::Format('icinga-powershell-{0}', $Name); + $UninstallPath = Join-Path -Path $ModuleBase -ChildPath $UninstallComponent; + + if ((Test-Path $UninstallPath) -eq $FALSE) { + Write-IcingaConsoleNotice -Message 'The Icinga for Windows component "{0}" at "{1}" could not ne found.' -Objects $UninstallComponent, $UninstallPath; + return $FALSE; + } + + Write-IcingaConsoleNotice -Message 'Uninstalling Icinga for Windows component "{0}" from "{1}"' -Objects $UninstallComponent, $UninstallPath; + if (Remove-ItemSecure -Path $UninstallPath -Recurse -Force) { + Write-IcingaConsoleNotice -Message 'Successfully removed Icinga for Windows component "{0}" from "{1}"' -Objects $UninstallComponent, $UninstallPath; + if ($UninstallComponent -ne 'icinga-powershell-framework') { + Remove-Module $UninstallComponent -Force -ErrorAction SilentlyContinue; + } + return $TRUE; + } else { + Write-IcingaConsoleError -Message 'Unable to uninstall Icinga for Windows component "{0}" from "{1}"' -Objects $UninstallComponent, $UninstallPath; + } + + return $FALSE; +} diff --git a/lib/core/repository/Unlock-IcingaComponent.psm1 b/lib/core/repository/Unlock-IcingaComponent.psm1 new file mode 100644 index 00000000..88d96fa5 --- /dev/null +++ b/lib/core/repository/Unlock-IcingaComponent.psm1 @@ -0,0 +1,25 @@ +function Unlock-IcingaComponent() +{ + param ( + [string]$Name = $null + ); + + if ([string]::IsNullOrEmpty($Name)) { + Write-IcingaConsoleError 'You have to specify the component to unlock'; + return; + } + + $LockedComponents = Get-IcingaPowerShellConfig -Path 'Framework.Repository.ComponentLock'; + + if ($null -eq $LockedComponents) { + Write-IcingaConsoleNotice 'You have currently no components which are locked configured'; + return; + } + + if (Test-IcingaPowerShellConfigItem -ConfigObject $LockedComponents -ConfigKey $Name) { + Remove-IcingaPowerShellConfig -Path ([string]::Format('Framework.Repository.ComponentLock.{0}', $Name)); + Write-IcingaConsoleNotice 'Unlocking of component "{0}" was successful.' -Objects $Name; + } else { + Write-IcingaConsoleNotice 'The component "{0}" is not locked on this system' -Objects $Name; + } +} diff --git a/lib/core/repository/Update-Icinga.psm1 b/lib/core/repository/Update-Icinga.psm1 new file mode 100644 index 00000000..233479ca --- /dev/null +++ b/lib/core/repository/Update-Icinga.psm1 @@ -0,0 +1,44 @@ +function Update-Icinga() +{ + param ( + [string]$Name = $null, + [switch]$Release = $FALSE, + [switch]$Snapshot = $FALSE, + [switch]$Confirm = $FALSE, + [switch]$Force = $FALSE + ); + + if ($Release -eq $FALSE -And $Snapshot -eq $FALSE) { + $Release = $TRUE; + } + + $CurrentInstallation = Get-IcingaInstallation -Release:$Release -Snapshot:$Snapshot; + + foreach ($entry in $CurrentInstallation.Keys) { + $Component = $CurrentInstallation[$entry]; + + if ([string]::IsNullOrEmpty($Name) -eq $FALSE -And $Name -ne $entry) { + continue; + } + + $NewVersion = $Component.LatestVersion; + + if ([string]::IsNullOrEmpty($NewVersion)) { + Write-IcingaConsoleNotice 'No update package found for component "{0}"' -Objects $entry; + continue; + } + + $LockedVersion = Get-IcingaComponentLock -Name $entry; + + if ($null -ne $LockedVersion) { + $NewVersion = $LockedVersion; + } + + if ([Version]$NewVersion -le [Version]$Component.CurrentVersion -And $Force -eq $FALSE) { + Write-IcingaConsoleNotice 'The installed version "{0}" of component "{1}" is identical or lower than the new version "{2}". Use "-Force" to install anyway' -Objects $Component.CurrentVersion, $entry, $NewVersion; + continue; + } + + Install-IcingaComponent -Name $entry -Version $NewVersion -Release:$Release -Snapshot:$Snapshot -Confirm:$Confirm -Force:$Force + } +} diff --git a/lib/core/repository/Update-IcingaRepository.psm1 b/lib/core/repository/Update-IcingaRepository.psm1 new file mode 100644 index 00000000..7137debe --- /dev/null +++ b/lib/core/repository/Update-IcingaRepository.psm1 @@ -0,0 +1,98 @@ +function Update-IcingaRepository() +{ + param ( + [string]$Name = $null, + [string]$Path = $null, + [string]$RemotePath = $null, + [switch]$CreateNew = $FALSE, + [switch]$ForceTrust = $FALSE + ); + + $CurrentRepositories = Get-IcingaPowerShellConfig -Path 'Framework.Repository.Repositories'; + + [array]$ConfigCount = $CurrentRepositories.PSObject.Properties.Count; + + if (($null -eq $CurrentRepositories -Or $ConfigCount.Count -eq 0) -And $CreateNew -eq $FALSE) { + Write-IcingaConsoleNotice 'There are no repositories configured yet. You can create a custom repository with "New-IcingaRepository" or clone an existing one with "Sync-IcingaForWindowsRepository"'; + return; + } + + if ([string]::IsNullOrEmpty($Name) -eq $FALSE) { + if ((Test-IcingaPowerShellConfigItem -ConfigObject $CurrentRepositories -ConfigKey $Name) -eq $FALSE -And $CreateNew -eq $FALSE) { + Write-IcingaConsoleError 'A repository with the given name "{0}" does not exist. Use "New-IcingaRepository" or "Sync-IcingaForWindowsRepository" to create a new one.' -Objects $Name; + return; + } + } + + foreach ($definedRepo in $CurrentRepositories.PSObject.Properties) { + + if ([string]::IsNullOrEmpty($Name) -eq $FALSE -And $definedRepo.Name.ToLower() -ne $Name.ToLower()) { + continue; + } + + if ($definedRepo.Value.Enabled -eq $FALSE) { + Write-IcingaConsoleNotice 'Skipping disabled repository "{0}"' -Objects $definedRepo.Name; + continue; + } + + if ([string]::IsNullOrEmpty($definedRepo.Value.CloneSource) -eq $FALSE) { + continue; + } + + if ([string]::IsNullOrEmpty($definedRepo.Value.LocalPath)) { + continue; + } + + if ((Test-Path $definedRepo.Value.LocalPath) -eq $FALSE) { + if ($CreateNew) { + return $null; + } + continue; + } + + $Path = Join-Path -Path $definedRepo.Value.LocalPath -ChildPath '\'; + + if ($CreateNew -eq $FALSE) { + Write-IcingaConsoleNotice 'Updating Icinga for Windows repository "{0}"' -Objects $definedRepo.Name; + } + + $IcingaRepository = New-IcingaRepositoryFile -Path $Path -RemotePath $RemotePath; + + if ($CreateNew) { + return $IcingaRepository; + } + } + + # Always sync repositories at the end, in case we updated a local repository and cloned it to somewhere else + foreach ($definedRepo in $CurrentRepositories.PSObject.Properties) { + + if ([string]::IsNullOrEmpty($Name) -eq $FALSE -And $definedRepo.Name.ToLower() -ne $Name.ToLower()) { + continue; + } + + if ($definedRepo.Value.Enabled -eq $FALSE) { + continue; + } + + if ([string]::IsNullOrEmpty($definedRepo.Value.LocalPath)) { + continue; + } + + Write-IcingaConsoleNotice 'Syncing repository "{0}"' -Objects $definedRepo.Name; + + if ([string]::IsNullOrEmpty($definedRepo.Value.CloneSource) -eq $FALSE) { + Sync-IcingaRepository ` + -Name $definedRepo.Name ` + -Path $definedRepo.Value.LocalPath ` + -RemotePath $definedRepo.Value.RemotePath ` + -Source $definedRepo.Value.CloneSource ` + -UseSCP:$definedRepo.Value.UseSCP ` + -Force ` + -ForceTrust:$ForceTrust; + + return; + } + } + + Write-IcingaConsoleNotice 'All Icinga for Windows repositories were successfully updated'; +} diff --git a/lib/core/tools/Add-IcingaWhiteSpaceToString.psm1 b/lib/core/tools/Add-IcingaWhiteSpaceToString.psm1 new file mode 100644 index 00000000..76d8103d --- /dev/null +++ b/lib/core/tools/Add-IcingaWhiteSpaceToString.psm1 @@ -0,0 +1,16 @@ +function Add-IcingaWhiteSpaceToString() +{ + param ( + [string]$Text = '', + [int]$Length = 0 + ); + + [int]$LengthOffset = $Length - $Text.Length; + + while ($LengthOffset -gt 0) { + $Text += ' '; + $LengthOffset -= 1; + } + + return $Text; +} diff --git a/lib/core/tools/Get-IcingaMaxTextLength.psm1 b/lib/core/tools/Get-IcingaMaxTextLength.psm1 new file mode 100644 index 00000000..bb4be246 --- /dev/null +++ b/lib/core/tools/Get-IcingaMaxTextLength.psm1 @@ -0,0 +1,16 @@ +function Get-IcingaMaxTextLength() +{ + param ( + [array]$TextArray = '' + ); + + [int]$MaxLength = 0; + + foreach ($text in $TextArray) { + if ($MaxLength -lt $text.Length) { + $MaxLength = $text.Length; + } + } + + return $MaxLength; +} diff --git a/lib/core/tools/Get-IcingaServices.psm1 b/lib/core/tools/Get-IcingaServices.psm1 index 710d4c4a..ab6b7397 100644 --- a/lib/core/tools/Get-IcingaServices.psm1 +++ b/lib/core/tools/Get-IcingaServices.psm1 @@ -11,7 +11,7 @@ function Get-IcingaServices() if ($Service.Count -eq 0) { $ServiceWmiInfo = Get-IcingaWindowsInformation Win32_Service; } else { - $ServiceWmiInfo = Get-IcingaWindowsInformation Win32_Service | Where-Object { $Service -Contains $_.Name } | Select-Object StartName, Name, ExitCode, StartMode; + $ServiceWmiInfo = Get-IcingaWindowsInformation Win32_Service | Where-Object { $Service -Contains $_.Name } | Select-Object StartName, Name, ExitCode, StartMode, PathName; } if ($null -eq $ServiceInformation) { @@ -26,6 +26,7 @@ function Get-IcingaServices() [array]$DependingServices = $null; $ServiceExitCode = 0; [string]$ServiceUser = ''; + [string]$ServicePath = ''; [int]$StartModeId = 5; [string]$StartMode = 'Unknown'; @@ -36,6 +37,7 @@ function Get-IcingaServices() foreach ($wmiService in $ServiceWmiInfo) { if ($wmiService.Name -eq $service.ServiceName) { $ServiceUser = $wmiService.StartName; + $ServicePath = $wmiService.PathName; $ServiceExitCode = $wmiService.ExitCode; if ([string]::IsNullOrEmpty($wmiService.StartMode) -eq $FALSE) { $StartModeId = ([int]$IcingaEnums.ServiceWmiStartupType[$wmiService.StartMode]); @@ -90,6 +92,7 @@ function Get-IcingaServices() 'value' = $StartMode; }; 'ServiceUser' = $ServiceUser; + 'ServicePath' = $ServicePath; 'ExitCode' = $ServiceExitCode; } }