Guest User

Untitled

a guest
Dec 10th, 2017
160
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 108.56 KB | None | 0 0
  1. function Icinga2AgentModule {
  2. #
  3. # Setup parameters which can be accessed
  4. # with -<ParamName>
  5. #
  6. param(
  7. # Agent setup
  8. [string]$AgentName,
  9. [string]$Ticket,
  10. [string]$InstallAgentVersion,
  11. [bool]$FetchAgentName = $TRUE,
  12. [bool]$FetchAgentFQDN = $TRUE,
  13. [int]$TransformHostname = 0,
  14.  
  15. # Agent configuration
  16. [int]$AgentListenPort = 5665,
  17. [string]$ParentZone,
  18. [bool]$AcceptConfig = $TRUE,
  19. [bool]$IcingaEnableDebugLog = $FALSE,
  20. [bool]$AgentAddFirewallRule = $TRUE,
  21. [array]$ParentEndpoints,
  22. [array]$EndpointsConfig,
  23. [array]$GlobalZones = @( 'director-global' ),
  24.  
  25. # Agent installation / update
  26. [string]$IcingaServiceUser = "NT AUTHORITY\SYSTEM",
  27. [string]$DownloadUrl = 'https://packages.icinga.com/windows/',
  28. [string]$AgentInstallDirectory,
  29. [bool]$AllowUpdates = $TRUE,
  30. [array]$InstallerHashes,
  31. [bool]$FlushApiDirectory = $TRUE,
  32.  
  33. # Agent signing
  34. [string]$CAServer,
  35. [int]$CAPort = 5665,
  36. [bool]$ForceCertificateGeneration = $FALSE,
  37. [string]$CAFingerprint,
  38.  
  39. # Director communication
  40. [string]$DirectorUrl,
  41. [string]$DirectorUser,
  42. [string]$DirectorPassword,
  43. [string]$DirectorDomain,
  44. [string]$DirectorAuthToken,
  45. [System.Object]$DirectorHostObject,
  46. [bool]$DirectorDeployConfig = $FALSE,
  47.  
  48. # NSClient Installer
  49. [bool]$InstallNSClient = $FALSE,
  50. [bool]$NSClientAddDefaults = $FALSE,
  51. [bool]$NSClientEnableFirewall = $FALSE,
  52. [bool]$NSClientEnableService = $FALSE,
  53. [string]$NSClientDirectory,
  54. [string]$NSClientInstallerPath,
  55.  
  56. # Uninstaller arguments
  57. [bool]$FullUninstallation = $TRUE,
  58. [bool]$RemoveNSClient = $TRUE,
  59.  
  60. #Internal handling
  61. [switch]$RunInstaller = $FALSE,
  62. [switch]$RunUninstaller = $FALSE,
  63. [bool]$DebugMode = $FALSE,
  64. [string]$ModuleLogFile
  65. );
  66.  
  67. #
  68. # Initialise our installer object
  69. # and generate our config objects
  70. #
  71. $installer = New-Object -TypeName PSObject;
  72. $installer | Add-Member -membertype NoteProperty -name 'properties' -value @{}
  73. $installer | Add-Member -membertype NoteProperty -name 'cfg' -value @{
  74. agent_name = $AgentName;
  75. ticket = $Ticket;
  76. agent_version = $InstallAgentVersion;
  77. fetch_agent_name = $FetchAgentName;
  78. fetch_agent_fqdn = $FetchAgentFQDN;
  79. transform_hostname = $TransformHostname;
  80. agent_listen_port = $AgentListenPort;
  81. parent_zone = $ParentZone;
  82. accept_config = $AcceptConfig;
  83. icinga_enable_debug_log = $IcingaEnableDebugLog;
  84. agent_add_firewall_rule = $AgentAddFirewallRule;
  85. parent_endpoints = $ParentEndpoints;
  86. endpoints_config = $EndpointsConfig;
  87. global_zones = $GlobalZones;
  88. icinga_service_user = $IcingaServiceUser;
  89. download_url = $DownloadUrl;
  90. agent_install_directory = $AgentInstallDirectory;
  91. allow_updates = $AllowUpdates;
  92. installer_hashes = $InstallerHashes;
  93. flush_api_directory = $FlushApiDirectory;
  94. ca_server = $CAServer;
  95. ca_port = $CAPort;
  96. force_cert = $ForceCertificateGeneration;
  97. ca_fingerprint = $CAFingerprint;
  98. director_url = $DirectorUrl;
  99. director_user = $DirectorUser;
  100. director_password = $DirectorPassword;
  101. director_domain = $DirectorDomain;
  102. director_auth_token = $DirectorAuthToken;
  103. director_host_object = $DirectorHostObject;
  104. director_deploy_config = $DirectorDeployConfig;
  105. install_nsclient = $InstallNSClient;
  106. nsclient_add_defaults = $NSClientAddDefaults;
  107. nsclient_firewall = $NSClientEnableFirewall;
  108. nsclient_service = $NSClientEnableService;
  109. nsclient_directory = $NSClientDirectory;
  110. nsclient_installer_path = $NSClientInstallerPath;
  111. full_uninstallation = $FullUninstallation;
  112. remove_nsclient = $RemoveNSClient;
  113. debug_mode = $DebugMode;
  114. module_log_file = $ModuleLogFile;
  115. }
  116.  
  117. #
  118. # Access default script config parameters
  119. # by using this function. These variables
  120. # are set during the initial call of
  121. # the script with the parameters
  122. #
  123. $installer | Add-Member -membertype ScriptMethod -name 'config' -value {
  124. param([string] $key);
  125. return $this.cfg[$key];
  126. }
  127.  
  128. #
  129. # Override the given arguments of the PowerShell script with
  130. # custom values or edited values
  131. #
  132. $installer | Add-Member -membertype ScriptMethod -name 'overrideConfig' -value {
  133. param([string] $key, $value);
  134. $this.cfg[$key] = $value;
  135. }
  136.  
  137. #
  138. # Convert a boolean value $TRUE $FALSE
  139. # to a string value
  140. #
  141. $installer | Add-Member -membertype ScriptMethod -name 'convertBoolToString' -value {
  142. param([bool]$key);
  143. if ($key) {
  144. return 'true';
  145. }
  146. return 'false';
  147. }
  148.  
  149. #
  150. # Convert a boolean value $TRUE $FALSE
  151. # to a int value
  152. #
  153. $installer | Add-Member -membertype ScriptMethod -name 'convertBoolToInt' -value {
  154. param([bool]$key);
  155. if ($key) {
  156. return 1;
  157. }
  158. return 0;
  159. }
  160.  
  161. #
  162. # Global variables can be accessed
  163. # by using this function. Example:
  164. # $this.getProperty('agent_version)
  165. #
  166. $installer | Add-Member -membertype ScriptMethod -name 'getProperty' -value {
  167. param([string] $key);
  168.  
  169. # Initialse some variables first
  170. # will only be called once
  171. if (-Not $this.properties.Get_Item('initialized')) {
  172. $this.init();
  173. }
  174.  
  175. return $this.properties.Get_Item($key);
  176. }
  177.  
  178. #
  179. # Set the value of a global variable
  180. # to ensure later usage. Example
  181. # $this.setProperty('agent_version', '2.4.10')
  182. #
  183. $installer | Add-Member -membertype ScriptMethod -name 'setProperty' -value {
  184. param([string]$key, $value);
  185.  
  186. # Initialse some variables first
  187. # will only be called once
  188. if (-Not $this.properties.Get_Item('initialized')) {
  189. $this.properties.Set_Item('initialized', $TRUE);
  190. $this.init();
  191. }
  192.  
  193. $this.properties.Set_Item($key, $value);
  194. }
  195.  
  196. #
  197. # This function will dump all global
  198. # variables of the script for debugging
  199. # purposes
  200. #
  201. $installer | Add-Member -membertype ScriptMethod -name 'dumpProperties' -value {
  202. Write-Output $this.properties;
  203. }
  204.  
  205. #
  206. # Write all output from consoles to a logfile
  207. #
  208. $installer | Add-Member -membertype ScriptMethod -name 'writeLogFile' -value {
  209. param([string]$severity, [string]$content);
  210.  
  211. # If no logfile is specified, do nothing
  212. if (-Not $this.config('module_log_file')) {
  213. return;
  214. }
  215.  
  216. # Store our logfile into a variable
  217. $logFile = $this.config('module_log_file');
  218.  
  219. # Have we specified a directory to write into or a file already?
  220. try {
  221. # Check if we are a directory or a file
  222. # Will return false for files or non-existing files
  223. $directory = (Get-Item $logFile) -is [System.IO.DirectoryInfo];
  224. } catch {
  225. # Nothing to catch. Simply get rid of error messages from aboves function in case of error
  226. # Will return false anyways on error
  227. }
  228.  
  229. # If we are a directory, add a file we can write to
  230. if ($directory) {
  231. $logFile = Join-Path -Path $logFile -ChildPath 'icinga2agent_psmodule.log';
  232. }
  233.  
  234. # Format a timestamp to get to know the exact date and time. Example: 2017-13-07 22:09:13.263.263
  235. $timestamp = Get-Date -Format "yyyy-dd-MM HH:mm:ss.fff";
  236. $content = [string]::Format('{0} [{1}]: {2}', $timestamp, $severity, $content);
  237.  
  238. # Write the content to our logfile
  239. Add-Content -Path $logFile -Value $content;
  240. }
  241.  
  242. #
  243. # This function will print messages as errors, but add them internally to
  244. # an exception list. These will re-printed at the end to summarize possible
  245. # issues during the run
  246. #
  247. $installer | Add-Member -membertype ScriptMethod -name 'exception' -value {
  248. param([string]$message, [string[]]$args);
  249. [array]$exceptions = $this.getProperty('exception_messages');
  250. if ($exceptions -eq $null) {
  251. $exceptions = @();
  252. }
  253. $exceptions += $message;
  254. $this.setProperty('exception_messages', $exceptions);
  255. write-host 'Fatal:' $message -ForegroundColor red;
  256. $this.writeLogFile('fatal', $message);
  257. }
  258.  
  259. #
  260. # Get the current exit code of the script. Return 0 for no errors and 1 for
  261. # possible errors, including a summary of what went wrong
  262. #
  263. $installer | Add-Member -membertype ScriptMethod -name 'getScriptExitCode' -value {
  264. [array]$exceptions = $this.getProperty('exception_messages');
  265.  
  266. if ($exceptions -eq $null) {
  267. return 0;
  268. }
  269.  
  270. $this.writeLogFile('fatal', '##################################################################');
  271. $message = '######## The script encountered several errors during run ########';
  272. $this.writeLogFile('fatal', $message);
  273. $this.writeLogFile('fatal', '##################################################################');
  274. write-host $message -ForegroundColor red;
  275. foreach ($err in $exceptions) {
  276. write-host 'Fatal:' $err -ForegroundColor red;
  277. $this.writeLogFile('fatal', $err);
  278. }
  279.  
  280. return 1;
  281. }
  282.  
  283. #
  284. # Print the relevant exception
  285. # By reading the relevant info
  286. # from the stack
  287. #
  288. $installer | Add-Member -membertype ScriptMethod -name 'printLastException' -value {
  289. # Todo: Improve this entire handling
  290. # for writing exception messages
  291. # in general we should only see
  292. # the actual thrown error instead of
  293. # an stack trace where the error occured
  294. #Write-Host $this.error($error[$error.count - 1].FullyQualifiedErrorId) -ForegroundColor red;
  295. Write-Host $_.Exception.Message -ForegroundColor red;
  296. }
  297.  
  298. #
  299. # this function will print an info message
  300. # or throw an exception, based on the
  301. # provided exitcode
  302. # (0 = ok, anything else => exception)
  303. #
  304. $installer | Add-Member -membertype ScriptMethod -name 'printAndAssertResultBasedOnExitCode' -value {
  305. param([string]$result, [string]$exitcode);
  306. if ($exitcode -ne 0) {
  307. throw $result;
  308. } else {
  309. $this.info($result);
  310. }
  311. }
  312.  
  313. #
  314. # Return an error message with red text
  315. #
  316. $installer | Add-Member -membertype ScriptMethod -name 'error' -value {
  317. param([string] $message, [array] $args);
  318. Write-Host 'Error:' $message -ForegroundColor red;
  319. $this.writeLogFile('error', $message);
  320. }
  321.  
  322. #
  323. # Return a warning message with yellow text
  324. #
  325. $installer | Add-Member -membertype ScriptMethod -name 'warn' -value {
  326. param([string] $message, [array] $args);
  327. Write-Host 'Warning:' $message -ForegroundColor yellow;
  328. $this.writeLogFile('warning', $message);
  329. }
  330.  
  331. #
  332. # Return a info message with green text
  333. #
  334. $installer | Add-Member -membertype ScriptMethod -name 'info' -value {
  335. param([string] $message, [array] $args);
  336. Write-Host 'Notice:' $message -ForegroundColor green;
  337. $this.writeLogFile('info', $message);
  338. }
  339.  
  340. #
  341. # Return a debug message with blue text
  342. # in case debug mode is enabled
  343. #
  344. $installer | Add-Member -membertype ScriptMethod -name 'debug' -value {
  345. param([string] $message, [array] $args);
  346. if ($this.config('debug_mode')) {
  347. Write-Host 'Debug:' $message -ForegroundColor blue;
  348. $this.writeLogFile('debug', $message);
  349. }
  350. }
  351.  
  352. #
  353. # Initialise certain parts of the
  354. # script first
  355. #
  356. $installer | Add-Member -membertype ScriptMethod -name 'init' -value {
  357. $this.setProperty('initialized', $TRUE);
  358. # Set the default config dir
  359. $this.setProperty('config_dir', (Join-Path -Path $Env:ProgramData -ChildPath 'icinga2\etc\icinga2\'));
  360. $this.setProperty('api_dir', (Join-Path -Path $Env:ProgramData -ChildPath 'icinga2\var\lib\icinga2\api'));
  361. $this.setProperty('icinga_ticket', $this.config('ticket'));
  362. $this.setProperty('local_hostname', $this.config('agent_name'));
  363. # Ensure we generate the required configuration content
  364. $this.generateConfigContent();
  365. }
  366.  
  367. #
  368. # We require to run this script as admin. Generate the required function here
  369. # We might run this script from a non-privileged user. Ensure we have admin
  370. # rights first. Otherwise abort the script.
  371. #
  372. $installer | Add-Member -membertype ScriptMethod -name 'isAdmin' -value {
  373. $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent();
  374. $principal = New-Object System.Security.Principal.WindowsPrincipal($identity);
  375.  
  376. if (-not $principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)) {
  377. throw 'You require to run this script as administrator.';
  378. return $FALSE;
  379. }
  380. return $TRUE;
  381. }
  382.  
  383. #
  384. # In case we want to define endpoint configuration (address / port)
  385. # we will require to fetch data correctly from a given array
  386. #
  387. $installer | Add-Member -membertype ScriptMethod -name 'getEndpointConfigurationByArrayIndex' -value {
  388. param([int] $currentIndex);
  389.  
  390. # Load the config into a local variable for quicker access
  391. [array]$endpoint_config = $this.config('endpoints_config');
  392.  
  393. # In case no endpoint config is given, we should do nothing
  394. if ($endpoint_config -eq $NULL) {
  395. return '';
  396. }
  397.  
  398. [string]$configArgument = $endpoint_config[$currentIndex];
  399. [string]$config_string = '';
  400. [array]$configObject = '';
  401.  
  402. if ($configArgument -ne '') {
  403. $configObject = $configArgument.Split(';');
  404. } else {
  405. return '';
  406. }
  407.  
  408. # Write the host data from the first array position
  409. if ($configObject[0]) {
  410. $config_string += ' host = "' + $configObject[0] +'"';
  411. }
  412.  
  413. # Write the port data from the second array position
  414. if ($configObject[1]) {
  415. $config_string += "`n"+' port = ' + $configObject[1];
  416. }
  417.  
  418. # Return the host and possible port configuration for this endpoint
  419. return $config_string;
  420. }
  421.  
  422. #
  423. # Build endpoint hosts and objects based
  424. # on configuration
  425. #
  426. $installer | Add-Member -membertype ScriptMethod -name 'generateEndpointNodes' -value {
  427.  
  428. if ($this.config('parent_endpoints')) {
  429. [string]$endpoint_objects = '';
  430. [string]$endpoint_nodes = '';
  431. [int]$endpoint_index = 0;
  432.  
  433. foreach ($endpoint in $this.config('parent_endpoints')) {
  434. $endpoint_objects += 'object Endpoint "' + "$endpoint" +'" {'+"`n";
  435. $endpoint_objects += $this.getEndpointConfigurationByArrayIndex($endpoint_index);
  436. $endpoint_objects += "`n" + '}' + "`n";
  437. $endpoint_nodes += '"' + "$endpoint" + '", ';
  438. $endpoint_index += 1;
  439. }
  440. # Remove the last blank and , from the string
  441. if (-Not $endpoint_nodes.length -eq 0) {
  442. $endpoint_nodes = $endpoint_nodes.Remove($endpoint_nodes.length - 2, 2);
  443. }
  444. $this.setProperty('endpoint_nodes', $endpoint_nodes);
  445. $this.setProperty('endpoint_objects', $endpoint_objects);
  446. $this.setProperty('generate_config', 'true');
  447. } else {
  448. $this.setProperty('generate_config', 'false');
  449. }
  450. }
  451.  
  452. #
  453. # Generate global zones by configuration
  454. #
  455. $installer | Add-Member -membertype ScriptMethod -name 'generateGlobalZones' -value {
  456.  
  457. # Load all configured global zones
  458. [array]$global_zones = $this.config('global_zones');
  459. [string]$zones = '';
  460.  
  461. # In case no zones are given, simply add director-global
  462. if ($global_zones -eq $NULL) {
  463. $this.setProperty('global_zones', $zones);
  464. return;
  465. }
  466.  
  467. # Loop through all given zones and add them to our configuration
  468. foreach ($zone in $global_zones) {
  469. if ($zone -ne '') {
  470. $zones = $zones + 'object Zone "' + $zone + '" {' + "`n" + ' global = true' + "`n" + '}' + "`n";
  471. }
  472. }
  473. $this.setProperty('global_zones', $zones);
  474. }
  475.  
  476. #
  477. # Generate default config values
  478. #
  479. $installer | Add-Member -membertype ScriptMethod -name 'generateConfigContent' -value {
  480. $this.generateEndpointNodes();
  481. $this.generateGlobalZones();
  482. }
  483.  
  484. #
  485. # This function will ensure we create a
  486. # Web Client object we can use entirely
  487. # inside the module to achieve our requirements
  488. #
  489. $installer | Add-Member -membertype ScriptMethod -name 'createWebClientInstance' -value {
  490. param([string]$header, [bool]$directorHeader = $FALSE);
  491.  
  492. [System.Object]$webClient = New-Object System.Net.WebClient;
  493. if ($this.config('director_user') -And $this.config('director_password')) {
  494. [string]$domain = $null;
  495. if ($this.config('director_domain')) {
  496. $domain = $this.config('director_domain');
  497. }
  498. $webClient.Credentials = New-Object System.Net.NetworkCredential($this.config('director_user'), $this.config('director_password'), $domain);
  499. }
  500. $webClient.Headers.add('accept', $header);
  501. if ($directorHeader) {
  502. $webClient.Headers.add('X-Director-Accept', 'text/plain');
  503. }
  504.  
  505. return $webClient;
  506. }
  507.  
  508. #
  509. # Handle HTTP Requests properly to receive proper status codes in return
  510. #
  511. $installer | Add-Member -membertype ScriptMethod -name 'createHTTPRequest' -value {
  512. param([string]$url, [string]$body, [string]$method, [string]$header, [bool]$directorHeader, [bool]$printExceptionMessage);
  513.  
  514. $httpRequest = [System.Net.HttpWebRequest]::Create($url);
  515. $httpRequest.Method = $method;
  516. $httpRequest.Accept = $header;
  517. $httpRequest.ContentType = 'application/json; charset=utf-8';
  518. if ($directorHeader) {
  519. $httpRequest.Headers.Add('X-Director-Accept: text/plain');
  520. }
  521. $httpRequest.TimeOut = 6000;
  522.  
  523. if ($this.config('director_user') -And $this.config('director_password')) {
  524. [string]$credentials = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($this.config('director_user') + ':' + $this.config('director_password')));
  525. $httpRequest.Headers.add('Authorization: Basic ' + $credentials);
  526. }
  527.  
  528. # Only send data in case we want to send some data
  529. if ($body -ne '') {
  530. $transmitBytes = [System.Text.Encoding]::UTF8.GetBytes($body);
  531. $httpRequest.ContentLength = $transmitBytes.Length;
  532. [System.IO.Stream]$httpOutput = [System.IO.Stream]$httpRequest.GetRequestStream()
  533. $httpOutput.Write($transmitBytes, 0, $transmitBytes.Length)
  534. $httpOutput.Close()
  535. }
  536.  
  537. try {
  538.  
  539. return $this.readResponseStream($httpRequest.GetResponse());
  540.  
  541. } catch [System.Net.WebException] {
  542. if ($printExceptionMessage) {
  543. # Print an exception message and the possible body in case we received one
  544. # to make troubleshooting easier
  545. [string]$errorResponse = $this.readResponseStream($_.Exception.Response);
  546. $this.error($_.Exception.Message);
  547. if ($errorResponse -ne '') {
  548. $this.error($errorResponse);
  549. }
  550. }
  551.  
  552. $exceptionMessage = $_.Exception.Response;
  553. $httpErrorCode = [int][system.net.httpstatuscode]$exceptionMessage.StatusCode;
  554. return $httpErrorCode;
  555. }
  556.  
  557. return '';
  558. }
  559.  
  560. #
  561. # Read the content of a response and return it's value as a string
  562. #
  563. $installer | Add-Member -membertype ScriptMethod -name 'readResponseStream' -value {
  564. param([System.Object]$response);
  565. $responseStream = $response.getResponseStream();
  566. $streamReader = New-Object IO.StreamReader($responseStream);
  567. $result = $streamReader.ReadToEnd();
  568. $response.close()
  569. $streamReader.close()
  570.  
  571. return $result;
  572. }
  573.  
  574. #
  575. # Check if the provided result is an HTTP Response code
  576. #
  577. $installer | Add-Member -membertype ScriptMethod -name 'isHTTPResponseCode' -value {
  578. param([string]$httpResult);
  579.  
  580. if ($httpResult.length -eq 3) {
  581. return $TRUE;
  582. }
  583.  
  584. return $FALSE;
  585. }
  586.  
  587. #
  588. # Do we require to update the Agent?
  589. # Might be disabled by user or current version
  590. # is already installed
  591. #
  592. $installer | Add-Member -membertype ScriptMethod -name 'requireAgentUpdate' -value {
  593. if (-Not $this.config('allow_updates') -Or -Not $this.config('agent_version')) {
  594. $this.warn('Icinga 2 Agent update installation disabled.');
  595. return $FALSE;
  596. }
  597.  
  598. if ($this.getProperty('agent_version') -eq $this.config('agent_version')) {
  599. $this.info('Icinga 2 Agent up-to-date. No update required.');
  600. return $FALSE;
  601. }
  602.  
  603. $this.info('Current Icinga 2 Agent Version (' + $this.getProperty('agent_version') + ') is not matching server version (' + $this.config('agent_version') + '). Downloading new version...');
  604.  
  605. return $TRUE;
  606. }
  607.  
  608. #
  609. # We could try to install the Agent from a local directory
  610. #
  611. $installer | Add-Member -membertype ScriptMethod -name 'isDownloadPathLocal' -value {
  612. if ($this.config('download_url') -And (Test-Path ($this.config('download_url')))) {
  613. return $TRUE;
  614. }
  615. return $FALSE;
  616. }
  617.  
  618. #
  619. # Download the Icinga 2 Agent Installer from out defined source
  620. #
  621. $installer | Add-Member -membertype ScriptMethod -name 'downloadInstaller' -value {
  622. if (-Not $this.config('agent_version')) {
  623. return;
  624. }
  625.  
  626. if ($this.isDownloadPathLocal()) {
  627. $this.info('Installing Icinga 2 Agent from local directory');
  628. } else {
  629. $url = $this.config('download_url') + $this.getProperty('install_msi_package');
  630. $this.info('Downloading Icinga 2 Agent Binary from ' + $url + ' ...');
  631.  
  632. Try {
  633. [System.Object]$client = New-Object System.Net.WebClient;
  634. $client.DownloadFile($url, $this.getInstallerPath());
  635.  
  636. if (-Not $this.installerExists()) {
  637. $this.exception('Unable to locate downloaded Icinga 2 Agent installer file from ' + $url + '. Download destination: ' + $this.getInstallerPath());
  638. }
  639. } catch {
  640. $this.exception('Unable to download Icinga 2 Agent from ' + $url + '. Please ensure the link does exist and access is possible. Error: ' + $_.Exception.Message);
  641. }
  642. }
  643. }
  644.  
  645. #
  646. # In case we provide a list of hashes to very against
  647. # we check them to ensure the package we downloaded
  648. # for the Agent installation is allowed to be installed
  649. #
  650. $installer | Add-Member -membertype ScriptMethod -name 'verifyInstallerChecksumAndThrowException' -value {
  651. if (-Not $this.config('installer_hashes')) {
  652. $this.warn("Icinga 2 Agent Installer verification disabled.");
  653. return;
  654. }
  655.  
  656. [string]$installerHash = $this.getInstallerFileHash($this.getInstallerPath());
  657. foreach($hash in $this.config('installer_hashes')) {
  658. if ($hash -eq $installerHash) {
  659. $this.info('Icinga 2 Agent hash verification successfull.');
  660. return;
  661. }
  662. }
  663.  
  664. throw 'Failed to verify against any provided installer hash.';
  665. return;
  666. }
  667.  
  668. #
  669. # Get the SHA1 hash from our uninstaller file
  670. # Own function required because Get-FileHash is not
  671. # supported by PowerShell Version 2
  672. #
  673. $installer | Add-Member -membertype ScriptMethod -name 'getInstallerFileHash' -value {
  674. param([string]$filename);
  675.  
  676. [System.Object]$fileInput = New-Object System.IO.FileStream($filename,[System.IO.FileMode]::Open);
  677. [System.Object]$hash = New-Object System.Text.StringBuilder;
  678. [System.Security.Cryptography.HashAlgorithm]::Create('SHA1').ComputeHash($fileInput) |
  679. ForEach-Object {
  680. [Void]$hash.Append($_.ToString("x2"));
  681. }
  682. $fileInput.Close();
  683. return $hash.ToString().ToUpper();
  684. }
  685.  
  686. #
  687. # Returns the full path to our installer package
  688. #
  689. $installer | Add-Member -membertype ScriptMethod -name 'getInstallerPath' -value {
  690. if (-Not $this.config('download_url') -Or -Not $this.getProperty('install_msi_package')) {
  691. return '';
  692. }
  693. $installerPath = Join-Path -Path $this.config('download_url') -ChildPath $this.getProperty('install_msi_package')
  694. if ($this.isDownloadPathLocal()) {
  695. if (Test-Path $installerPath) {
  696. return $installerPath;
  697. } else {
  698. $this.exception('Failed to locate local Icinga 2 Agent installer at ' + $installerPath);
  699. return '';
  700. }
  701. } else {
  702. return (Join-Path -Path $Env:temp -ChildPath $this.getProperty('install_msi_package'));
  703. }
  704. }
  705.  
  706. #
  707. # Verify that the installer package we downloaded
  708. # does exist in first place
  709. #
  710. $installer | Add-Member -membertype ScriptMethod -name 'installerExists' -value {
  711. if ($this.getInstallerPath() -And (Test-Path $this.getInstallerPath())) {
  712. return $TRUE;
  713. }
  714. return $FALSE;
  715. }
  716.  
  717. #
  718. # Get all arguments for the Icinga 2 Agent installer package
  719. #
  720. $installer | Add-Member -membertype ScriptMethod -name 'getIcingaAgentInstallerArguments' -value {
  721. # Initialise some basic variables
  722. [string]$arguments = '';
  723. [string]$installerLocation = '';
  724.  
  725. # By default, install the Icinga 2 Agent again in the pre-installed directory
  726. # before the update. Will only apply during updates / downgrades of the Agent
  727. if ($this.getProperty('cur_install_dir')) {
  728. $installerLocation = [string]::Format(' INSTALL_ROOT="{0}"', $this.getProperty('cur_install_dir'));
  729. }
  730.  
  731. # However, if we specified a custom directory over the argument, always use that
  732. # one as installer target directory
  733. if ($this.config('agent_install_directory')) {
  734. $installerLocation = [string]::Format(' INSTALL_ROOT="{0}"', $this.config('agent_install_directory'));
  735. $this.setProperty('cur_install_dir', $this.config('agent_install_directory'));
  736. }
  737.  
  738. $arguments += $installerLocation;
  739.  
  740. return $arguments;
  741. }
  742.  
  743. #
  744. # Install the Icinga 2 agent from the provided installation package
  745. #
  746. $installer | Add-Member -membertype ScriptMethod -name 'installAgent' -value {
  747. $this.downloadInstaller();
  748. if (-Not $this.installerExists()) {
  749. $this.exception('Failed to setup Icinga 2 Agent. Installer package not found.');
  750. return;
  751. }
  752. $this.verifyInstallerChecksumAndThrowException();
  753. $this.info('Installing Icinga 2 Agent...');
  754.  
  755. # Start the installer process
  756. $result = $this.startProcess('MsiExec.exe', $TRUE, [string]::Format('/quiet /i "{0}" {1}', $this.getInstallerPath(), $this.getIcingaAgentInstallerArguments()));
  757.  
  758. # Exit Code 0 means the Agent was installed successfully
  759. # Otherwise we require to throw an error
  760. if ($result.Get_Item('exitcode') -ne 0) {
  761. $this.exception('Failed to install Icinga 2 Agent. ' + $result.Get_Item('message'));
  762. } else {
  763. $this.info('Icinga 2 Agent installed.');
  764. }
  765.  
  766. $this.setProperty('require_restart', 'true');
  767. }
  768.  
  769. #
  770. # Updates the Agent in case allowed and required.
  771. # Removes previous version of Icinga 2 Agent first
  772. #
  773. $installer | Add-Member -membertype ScriptMethod -name 'updateAgent' -value {
  774. $this.downloadInstaller();
  775. if (-Not $this.installerExists()) {
  776. $this.exception('Failed to update Icinga 2 Agent. Installer package not found.');
  777. return;
  778. }
  779. $this.verifyInstallerChecksumAndThrowException()
  780. if (-Not $this.getProperty('uninstall_id')) {
  781. $this.exception('Failed to update Icinga 2 Agent. Uninstaller is not specified.');
  782. return;
  783. }
  784.  
  785. $this.info('Removing previous Icinga 2 Agent version...');
  786. # Start the uninstaller process
  787. $result = $this.startProcess('MsiExec.exe', $TRUE, $this.getProperty('uninstall_id') +' /q');
  788.  
  789. # Exit Code 0 means the Agent was removed successfully
  790. # Otherwise we require to throw an error
  791. if ($result.Get_Item('exitcode') -ne 0) {
  792. $this.exception('Failed to remove Icinga 2 Agent. ' + $result.Get_Item('message'));
  793. } else {
  794. $this.info('Icinga 2 Agent successfully removed.');
  795. }
  796.  
  797. $this.info('Installing new Icinga 2 Agent version...');
  798. # Start the installer process
  799. $result = $this.startProcess('MsiExec.exe', $TRUE, [string]::Format('/quiet /i "{0}" {1}', $this.getInstallerPath(), $this.getIcingaAgentInstallerArguments()));
  800.  
  801. # Exit Code 0 means the Agent was removed successfully
  802. # Otherwise we require to throw an error
  803. if ($result.Get_Item('exitcode') -ne 0) {
  804. $this.exception('Failed to install new Icinga 2 Agent. ' + $result.Get_Item('message'));
  805. } else {
  806. $this.info('Icinga 2 Agent successfully updated.');
  807. }
  808.  
  809. $this.setProperty('require_restart', 'true');
  810. }
  811.  
  812. #
  813. # We might have installed the Icinga 2 Agent
  814. # already. In case we do, get all data to
  815. # ensure we access the Agent correctly
  816. #
  817. $installer | Add-Member -membertype ScriptMethod -name 'isAgentInstalled' -value {
  818. [string]$architecture = '';
  819. if ([IntPtr]::Size -eq 4) {
  820. $architecture = "x86";
  821. $regPath = 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*';
  822. } else {
  823. $architecture = "x86_64";
  824. $regPath = @('HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*', 'HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*');
  825. }
  826.  
  827. # Try locating current Icinga 2 Agent installation
  828. $localData = Get-ItemProperty $regPath |
  829. .{
  830. process {
  831. if ($_.DisplayName) {
  832. $_;
  833. }
  834. }
  835. } |
  836. Where-Object {
  837. $_.DisplayName -eq 'Icinga 2';
  838. } |
  839. Select-Object -Property InstallLocation, UninstallString, DisplayVersion;
  840.  
  841. if ($localData.UninstallString) {
  842. $this.setProperty('uninstall_id', $localData.UninstallString.Replace("MsiExec.exe ", ""));
  843. }
  844. $this.setProperty('cur_install_dir', $localData.InstallLocation);
  845. $this.setProperty('agent_version', $localData.DisplayVersion);
  846. $this.setProperty('install_msi_package', 'Icinga2-v' + $this.config('agent_version') + '-' + $architecture + '.msi');
  847.  
  848. if ($localData.InstallLocation) {
  849. $this.info('Found Icinga 2 Agent version ' + $localData.DisplayVersion + ' installed at ' + $localData.InstallLocation);
  850. return $TRUE;
  851. } else {
  852. $this.warn('Icinga 2 Agent does not seem to be installed on the system');
  853. # Set Default value for install dir
  854. $this.setProperty('cur_install_dir', (Join-Path $Env:ProgramFiles -ChildPath 'ICINGA2'));
  855. }
  856. return $FALSE;
  857. }
  858.  
  859. #
  860. # Ensure we are able to install a firewall rule for the Icinga 2 Agent,
  861. # allowing masters and satellites to connect to our local agent
  862. #
  863. $installer | Add-Member -membertype ScriptMethod -name 'installIcingaAgentFirewallRule' -value {
  864. if ($this.config('agent_add_firewall_rule') -eq $FALSE) {
  865. $this.warn('Icinga 2 Agent Firewall Rule will not be installed.');
  866. return;
  867. }
  868.  
  869. $this.info('Trying to install Icinga 2 Agent Firewall Rule for port ' + $this.config('agent_listen_port'));
  870.  
  871. $result = $this.startProcess('netsh', $FALSE, 'advfirewall firewall show rule name="Icinga 2 Agent Inbound by PS-Module"');
  872. if ($result.Get_Item('exitcode') -eq 0) {
  873. # Firewall rule is already defined -> delete it and add it again
  874.  
  875. $this.info('Icinga 2 Agent Firewall Rule already installed. Trying to remove it to add it again...');
  876. $result = $this.startProcess('netsh', $TRUE, 'advfirewall firewall delete rule name="Icinga 2 Agent Inbound by PS-Module"');
  877.  
  878. if ($result.Get_Item('exitcode') -ne 0) {
  879. $this.error('Failed to remove Icinga 2 Agent Firewall rule before adding it again: ' + $result.Get_Item('message'));
  880. return;
  881. } else {
  882. $this.info('Icinga 2 Agent Firewall Rule has been removed. Re-Adding now...');
  883. }
  884. }
  885.  
  886. [string]$argument = 'advfirewall firewall add rule'
  887. $argument = $argument + ' dir=in action=allow program="' + $this.getInstallPath() + 'sbin\icinga2.exe"';
  888. $argument = $argument + ' name="Icinga 2 Agent Inbound by PS-Module"';
  889. $argument = $argument + ' description="Inbound Firewall Rule to allow Icinga 2 masters/satellites to connect to the Icinga 2 Agent installed on this system."';
  890. $argument = $argument + ' enable=yes';
  891. $argument = $argument + ' remoteip=any';
  892. $argument = $argument + ' localip=any';
  893. $argument = $argument + ' localport=' + $this.config('agent_listen_port');
  894. $argument = $argument + ' protocol=tcp';
  895.  
  896. $result = $this.startProcess('netsh', $FALSE, $argument);
  897. if ($result.Get_Item('exitcode') -ne 0) {
  898. # Firewall rule was not added -> print error
  899. $this.error('Failed to install Icinga 2 Agent Firewall: ' + $result.Get_Item('message'));
  900. return;
  901. }
  902.  
  903. $this.info('Icinga 2 Agent Firewall Rule successfully installed for port ' + $this.config('agent_listen_port'));
  904. }
  905.  
  906. #
  907. # Get the default path or our custom path for the NSClient++
  908. #
  909. $installer | Add-Member -membertype ScriptMethod -name 'getNSClientDefaultExecutablePath' -value {
  910.  
  911. if ($this.config('nsclient_directory')) {
  912. return (Join-Path -Path $this.config('nsclient_directory') -ChildPath 'nscp.exe');
  913. }
  914.  
  915. if (Test-Path ('C:\Program Files\NSClient++\nscp.exe')) {
  916. return 'C:\Program Files\NSClient++\nscp.exe';
  917. }
  918.  
  919. if (Test-Path ('C:\Program Files (x86)\NSClient++\nscp.exe')) {
  920. return 'C:\Program Files (x86)\NSClient++\nscp.exe';
  921. }
  922.  
  923. return '';
  924. }
  925.  
  926. #
  927. # In case have the Agent already installed
  928. # We might use a different installation path
  929. # for the Agent. This function will return
  930. # the correct, valid installation path
  931. #
  932. $installer | Add-Member -membertype ScriptMethod -name 'getInstallPath' -value {
  933. [string]$agentPath = '';
  934. if ($this.getProperty('cur_install_dir')) {
  935. $agentPath = $this.getProperty('cur_install_dir');
  936. }
  937. return $agentPath;
  938. }
  939.  
  940. #
  941. # In case we installed the agent freshly we
  942. # require to change configuration once we
  943. # would like to use the Director properly
  944. # This function will simply do a backup
  945. # of the icinga2.conf, ensuring we can
  946. # use them later again
  947. #
  948. $installer | Add-Member -membertype ScriptMethod -name 'backupDefaultConfig' -value {
  949. [string]$configFile = Join-Path -Path $this.getProperty('config_dir') -ChildPath 'icinga2.conf';
  950. [string]$configBackupFile = $configFile + 'director.bak';
  951.  
  952. # Check if a config and backup file already exists
  953. # Only procceed with backup of the current config if no backup was found
  954. if (Test-Path $configFile) {
  955. if (-Not (Test-Path $configBackupFile)) {
  956. Rename-Item $configFile $configBackupFile;
  957. $this.info('Icinga 2 configuration backup successfull');
  958. } else {
  959. $this.warn('Default icinga2.conf backup detected. Skipping backup');
  960. }
  961. }
  962. }
  963.  
  964. #
  965. # Allow us to restart the Icinga 2 Agent
  966. #
  967. $installer | Add-Member -membertype ScriptMethod -name 'cleanupAgentInstaller' -value {
  968. if (-Not ($this.isDownloadPathLocal())) {
  969. if ($this.getInstallerPath() -And (Test-Path $this.getInstallerPath())) {
  970. $this.info('Removing downloaded Icinga 2 Agent installer');
  971. Remove-Item $this.getInstallerPath() | out-null;
  972. }
  973. }
  974. }
  975.  
  976. #
  977. # Get Api directory if Icinga 2
  978. #
  979. $installer | Add-Member -membertype ScriptMethod -name 'getApiDirectory' -value {
  980. return $this.getProperty('api_dir');
  981. }
  982.  
  983. #
  984. # Should we remove the Api directory content
  985. # from the Agent? Can be defined by setting the
  986. # -RemoveApiDirectory argument of the function builder
  987. #
  988. $installer | Add-Member -membertype ScriptMethod -name 'shouldFlushIcingaApiDirectory' -value {
  989. return $this.config('flush_api_directory');
  990. }
  991.  
  992. #
  993. # Flush all content from the Icinga 2 Agent
  994. # Api directory, but keep the folder structure
  995. #
  996. $installer | Add-Member -membertype ScriptMethod -name 'flushIcingaApiDirectory' -value {
  997. if (Test-Path $this.getApiDirectory()) {
  998. $this.info('Flushing content of ' + $this.getApiDirectory());
  999. [System.Object]$folder = New-Object -ComObject Scripting.FileSystemObject;
  1000. $folder.DeleteFolder($this.getApiDirectory());
  1001. $this.setProperty('require_restart', 'true');
  1002. }
  1003. }
  1004.  
  1005. #
  1006. # Modify the user the Icinga services is running with
  1007. #
  1008. $installer | Add-Member -membertype ScriptMethod -name 'modifyIcingaServiceUser' -value {
  1009.  
  1010. # If no user is specified -> do nothing
  1011. if ($this.config('icinga_service_user') -eq '') {
  1012. return;
  1013. }
  1014.  
  1015. [System.Object]$currentUser = Get-WMIObject win32_service -Filter "Name='icinga2'";
  1016. [string]$credentials = $this.config('icinga_service_user');
  1017. [string]$newUser = '';
  1018. [string]$password = 'dummy';
  1019.  
  1020. if ($currentUser -eq $null) {
  1021. $this.warn('Unable to modify Icinga service user: Service not found.');
  1022. return;
  1023. }
  1024.  
  1025. # Check if we defined user name and password (':' cannot appear within a username)
  1026. # If so split them into seperate variables, otherwise simply use the string as user
  1027. if ($credentials.Contains(':')) {
  1028. [int]$delimeter = $credentials.IndexOf(':');
  1029. $newUser = $credentials.Substring(0, $delimeter);
  1030. $password = $credentials.Substring($delimeter + 1, $credentials.Length - 1 - $delimeter);
  1031. } else {
  1032. $newUser = $credentials;
  1033. }
  1034.  
  1035. # If the user's are identical -> do nothing
  1036. if ($currentUser.StartName -eq $newUser) {
  1037. $this.info('Icinga user was not modified. Source and target service user are identical.');
  1038. return;
  1039. }
  1040.  
  1041. # Try to update the service name and return an error in case of a failure
  1042. # In the error case we do not have to deal with cleanup, as no change was made anyway
  1043. $this.info('Updating Icinga 2 service user to ' + $newUser);
  1044. $result = $this.startProcess('sc.exe', $TRUE, 'config icinga2 obj="' + $newUser + '" ' + 'password=' + $password);
  1045.  
  1046. if ($result.Get_Item('exitcode') -ne 0) {
  1047. $this.error($result.Get_Item('message'));
  1048. return;
  1049. }
  1050.  
  1051. # Just write the success message
  1052. $this.info($result.Get_Item('message'));
  1053.  
  1054. # Try to restart the service
  1055. $result = $this.restartService('icinga2');
  1056.  
  1057. # In case of an error try to rollback to the previous assigned user of the service
  1058. # If this fails aswell, set the user to 'LocalSystem' and restart the service to
  1059. # ensure that the agent is atleast running and collecting some data.
  1060. # Of course we throw plenty of errors to notify the user about problems
  1061. if ($result.Get_Item('exitcode') -ne 0) {
  1062. $this.error($result.Get_Item('message'));
  1063. $this.info('Reseting user to previous working user ' + $currentUser.StartName);
  1064. $result = $this.startProcess('sc.exe', $TRUE, 'config icinga2 obj="' + $currentUser.StartName + '" ' + 'password=' + $password);
  1065. $result = $this.restartService('icinga2');
  1066. if ($result.Get_Item('exitcode') -ne 0) {
  1067. $this.error('Failed to reset Icinga 2 service user to the previous user ' + $currentUser.StartName + '. Setting user to "LocalSystem" now to ensure the service integrity');
  1068. $result = $this.startProcess('sc.exe', $TRUE, 'config icinga2 obj="LocalSystem" password=dummy');
  1069. $this.info($result.Get_Item('message'));
  1070. $result = $this.restartService('icinga2');
  1071. if ($result.Get_Item('exitcode') -eq 0) {
  1072. $this.info('Reseting Icinga 2 service user to "LocalSystem" successfull.');
  1073. return;
  1074. } else {
  1075. $this.error('Failed to rollback Icinga 2 service user to "LocalSystem": ' + $result.Get_Item('message'));
  1076. return;
  1077. }
  1078. }
  1079. }
  1080.  
  1081. $this.info('Icinga 2 service is running');
  1082. }
  1083.  
  1084. #
  1085. # Function to make restart of services easier
  1086. #
  1087. $installer | Add-Member -membertype ScriptMethod -name 'restartService' -value {
  1088. param([string]$service);
  1089.  
  1090. $this.info('Restarting service ' + $service + '...');
  1091.  
  1092. # Stop the current service
  1093. $result = $this.startProcess("sc.exe", $TRUE, "stop $service");
  1094.  
  1095. # Wait until the service is stopped
  1096. $serviceResult = $this.waitForServiceToReachState($service, 'Stopped');
  1097.  
  1098. # Start the service again
  1099. $result = $this.startProcess("sc.exe", $TRUE, "start $service");
  1100.  
  1101. # Wait until the service is started
  1102. if ($this.waitForServiceToReachState($service, 'Running') -eq $FALSE) {
  1103. $result.Set_Item('message', 'Failed to restart service ' + $service + '.');
  1104. $result.Set_Item('exitcode', '1');
  1105. }
  1106.  
  1107. return $result;
  1108. }
  1109.  
  1110. #
  1111. # This function will wait for a specific service until it reaches
  1112. # the defined state. Will break after 20 seconds with an error message
  1113. #
  1114. $installer | Add-Member -membertype ScriptMethod -name 'waitForServiceToReachState' -value {
  1115. param([string]$service, [string]$state);
  1116.  
  1117. [int]$counter = 0;
  1118.  
  1119. # Wait until the service reached the desired state
  1120. while ($TRUE) {
  1121.  
  1122. # Get the current state of the service
  1123. $serviceState = (Get-WMIObject win32_service -Filter "Name='$service'").State;
  1124. if ($serviceState -eq $state) {
  1125. break;
  1126. }
  1127.  
  1128. # Sleep a little to prevent crushing the CPU
  1129. Start-Sleep -Milliseconds 100;
  1130. $counter += 1;
  1131.  
  1132. # After 20 seconds break with an error. It look's like the service does not respond
  1133. if ($counter -gt 200) {
  1134. $this.error('Timeout reached while waiting for ' + $service + ' to reach state ' + $state + '. Service is not responding.');
  1135. return $FALSE;
  1136. }
  1137. }
  1138.  
  1139. # Wait one second and check the status again to ensure it remains within it's state
  1140. Start-Sleep -Seconds 1;
  1141.  
  1142. if ($state -ne (Get-WMIObject win32_service -Filter "Name='$service'").State) {
  1143. return $FALSE;
  1144. }
  1145.  
  1146. return $TRUE;
  1147. }
  1148.  
  1149. #
  1150. # Function to start processes and wait for their exit
  1151. # Will return a dictionary with results (message, error, exitcode)
  1152. #
  1153. $installer | Add-Member -membertype ScriptMethod -name 'startProcess' -value {
  1154. param([string]$executable, [bool]$flushNewLines, [string]$arguments);
  1155.  
  1156. $processData = New-Object System.Diagnostics.ProcessStartInfo;
  1157. $processData.FileName = $executable;
  1158. $processData.RedirectStandardError = $true;
  1159. $processData.RedirectStandardOutput = $true;
  1160. $processData.UseShellExecute = $false;
  1161. $processData.Arguments = $arguments;
  1162. $process = New-Object System.Diagnostics.Process;
  1163. $process.StartInfo = $processData;
  1164. $process.Start() | Out-Null;
  1165. $stdout = $process.StandardOutput.ReadToEnd();
  1166. $stderr = $process.StandardError.ReadToEnd();
  1167. $process.WaitForExit();
  1168.  
  1169. if ($flushNewLines) {
  1170. $stdout = $stdout.Replace("`n", '').Replace("`r", '');
  1171. $stderr = $stderr.Replace("`n", '').Replace("`r", '');
  1172. } else {
  1173. if ($stdout.Contains("`n")) {
  1174. $stdout = $stdout.Substring(0, $stdout.LastIndexOf("`n"));
  1175. }
  1176. }
  1177.  
  1178. $result = @{};
  1179. $result.Add('message', $stdout);
  1180. $result.Add('error', $stderr);
  1181. $result.Add('exitcode', $process.ExitCode);
  1182.  
  1183. return $result;
  1184. }
  1185.  
  1186. #
  1187. # Restart the Icinga 2 service and get the
  1188. # result if the restart failed or everything
  1189. # worked as expected
  1190. #
  1191. $installer | Add-Member -membertype ScriptMethod -name 'restartAgent' -value {
  1192. $result = $this.restartService('icinga2');
  1193.  
  1194. if ($result.Get_Item('exitcode') -eq 0) {
  1195. $this.info('Icinga 2 Agent successfully restarted.');
  1196. $this.setProperty('require_restart', '');
  1197. } else {
  1198. $this.error($result.Get_Item('message'));
  1199. }
  1200. }
  1201.  
  1202. $installer | Add-Member -membertype ScriptMethod -name 'generateIcingaConfiguration' -value {
  1203. if ($this.getProperty('generate_config') -eq 'true') {
  1204.  
  1205. $this.checkConfigInputParametersAndThrowException();
  1206.  
  1207. [string]$icingaCurrentConfig = '';
  1208. if (Test-Path $this.getIcingaConfigFile()) {
  1209. $icingaCurrentConfig = [System.IO.File]::ReadAllText($this.getIcingaConfigFile());
  1210. }
  1211.  
  1212. [string]$icingaNewConfig =
  1213. '/**
  1214. * Icinga 2 Config - Proposed by Icinga 2 PowerShell Module
  1215. */
  1216.  
  1217. /* Define our includes to run the agent properly. */
  1218. include "constants.conf"
  1219. include <itl>
  1220. include <plugins>
  1221. include <nscp>
  1222. include <windows-plugins>
  1223.  
  1224. /* Define our block required to enable or disable Icinga 2 debug log
  1225. * Enable or disable it by using the PowerShell Module with
  1226. * argument -IcingaEnableDebugLog or by switching
  1227. * PowerShellIcinga2EnableDebug to true or false manually.
  1228. * true: Debug log is active
  1229. * false: Debug log is deactivated
  1230. * IMPORTANT: ";" after true or false has to remain to allow the
  1231. * PowerShell Module to switch this feature on or off.
  1232. */
  1233. const PowerShellIcinga2EnableDebug = false;
  1234. if (PowerShellIcinga2EnableDebug) {
  1235. object FileLogger "debug-file" {
  1236. severity = "debug"
  1237. path = LocalStateDir + "/log/icinga2/debug.log"
  1238. }
  1239. }
  1240.  
  1241. /* Try to define a constant for our NSClient++ installation
  1242. * IMPORTANT: If the NSClient++ is installed newly to the system, the
  1243. * Icinga Service has to be restarted in order to set this variable
  1244. * correctly. If the NSClient++ is installed over the PowerShell Module,
  1245. * the Icinga 2 Service is restarted automaticly.
  1246. */
  1247. if (!globals.contains("NscpPath")) {
  1248. NscpPath = dirname(msi_get_component_path("{5C45463A-4AE9-4325-96DB-6E239C034F93}"))
  1249. }
  1250.  
  1251. /* Enable our default main logger feature to write log output. */
  1252. object FileLogger "main-log" {
  1253. severity = "information"
  1254. path = LocalStateDir + "/log/icinga2/icinga2.log"
  1255. }
  1256.  
  1257. /* All informations required to correctly connect to our parent Icinga 2 nodes. */
  1258. object Endpoint "' + $this.getProperty('local_hostname') + '" {}
  1259. ' + $this.getProperty('endpoint_objects') + '
  1260. /* Define the zone and its containing endpoints we should communicate with. */
  1261. object Zone "' + $this.config('parent_zone') + '" {
  1262. endpoints = [ ' + $this.getProperty('endpoint_nodes') +' ]
  1263. }
  1264.  
  1265. /* All of our global zones, check commands and other configuration are synced into.
  1266. * Director global zone must be defined in case the Icinga Director is beeing used.
  1267. * Default value for this is "director-global".
  1268. * All additional zones can be configured with -GlobalZones argument.
  1269. * IMPORTANT: If -GlobalZones argument is used, the Icinga Director global zones has
  1270. * to be defined as well within the argument array.
  1271. */
  1272. ' + $this.getProperty('global_zones') + '
  1273. /* Define a zone for our current agent and set our parent zone for proper communication. */
  1274. object Zone "' + $this.getProperty('local_hostname') + '" {
  1275. parent = "' + $this.config('parent_zone') + '"
  1276. endpoints = [ "' + $this.getProperty('local_hostname') + '" ]
  1277. }
  1278.  
  1279. /* Configure all settings we require for our API listener to properly work.
  1280. * This will include the certificates, if we accept configurations which
  1281. * can be changed with argument -AcceptConfig and the bind informations.
  1282. * The bind_port can be modified with argument -AgentListenPort.
  1283. */
  1284. object ApiListener "api" {
  1285. cert_path = SysconfDir + "/icinga2/pki/' + $this.getProperty('local_hostname') + '.crt"
  1286. key_path = SysconfDir + "/icinga2/pki/' + $this.getProperty('local_hostname') + '.key"
  1287. ca_path = SysconfDir + "/icinga2/pki/ca.crt"
  1288. accept_commands = true
  1289. accept_config = ' + $this.convertBoolToString($this.config('accept_config')) + '
  1290. bind_host = "::"
  1291. bind_port = ' + [int]$this.config('agent_listen_port') + '
  1292. }';
  1293.  
  1294. $this.setProperty('new_icinga_config', $icingaNewConfig);
  1295. $this.setProperty('old_icinga_config', $icingaCurrentConfig);
  1296. }
  1297. }
  1298.  
  1299. #
  1300. # Generate a hash for old and new config
  1301. # and determine if the configuration has changed
  1302. #
  1303. $installer | Add-Member -membertype ScriptMethod -name 'hasConfigChanged' -value {
  1304.  
  1305. if ($this.getProperty('generate_config') -eq 'false') {
  1306. return $FALSE;
  1307. }
  1308. if (-Not $this.getProperty('new_icinga_config')) {
  1309. throw 'New Icinga 2 configuration not generated. Please call "generateIcingaConfiguration" before.';
  1310. }
  1311.  
  1312. [string]$oldConfigHash = $this.getHashFromString($this.getProperty('old_icinga_config'));
  1313. [string]$newConfigHash = $this.getHashFromString($this.getProperty('new_icinga_config'));
  1314.  
  1315. $this.debug('Old Config Hash: "' + $oldConfigHash + '" New Hash: "' + $newConfigHash + '"');
  1316.  
  1317. if ($oldConfigHash -eq $newConfigHash) {
  1318. return $FALSE;
  1319. }
  1320.  
  1321. return $TRUE;
  1322. }
  1323.  
  1324. #
  1325. # Generate a SHA1 Hash from a provided string
  1326. #
  1327. $installer | Add-Member -membertype ScriptMethod -name 'getHashFromString' -value {
  1328. param([string]$text);
  1329. [System.Object]$algorithm = New-Object System.Security.Cryptography.SHA1Managed;
  1330. $hash = [System.Text.Encoding]::UTF8.GetBytes($text);
  1331. $hashInBytes = $algorithm.ComputeHash($hash);
  1332. [string]$result = '';
  1333. foreach($byte in $hashInBytes) {
  1334. $result += $byte.ToString();
  1335. }
  1336. return $result;
  1337. }
  1338.  
  1339. #
  1340. # Return the path to the Icinga 2 config file
  1341. #
  1342. $installer | Add-Member -membertype ScriptMethod -name 'getIcingaConfigFile' -value {
  1343. return (Join-Path -Path $this.getProperty('config_dir') -ChildPath 'icinga2.conf');
  1344. }
  1345.  
  1346. #
  1347. # Create Icinga 2 configuration file based
  1348. # on Director settings
  1349. #
  1350. $installer | Add-Member -membertype ScriptMethod -name 'writeConfig' -value {
  1351. param([string]$configData);
  1352.  
  1353. if (-Not (Test-Path $this.getProperty('config_dir'))) {
  1354. $this.warn('Unable to write Icinga 2 configuration. The required directory was not found. Possibly the Icinga 2 Agent is not installed.');
  1355. return;
  1356. }
  1357.  
  1358. # Write new configuration to file
  1359. $this.info('Writing icinga2.conf to ' + $this.getProperty('config_dir'));
  1360. [System.IO.File]::WriteAllText($this.getIcingaConfigFile(), $configData);
  1361. $this.setProperty('require_restart', 'true');
  1362. }
  1363.  
  1364. #
  1365. # Write old coniguration again
  1366. # just in case we received errors
  1367. #
  1368. $installer | Add-Member -membertype ScriptMethod -name 'rollbackConfig' -value {
  1369. # Write new configuration to file
  1370. $this.info('Rolling back previous icinga2.conf to ' + $this.getProperty('config_dir'));
  1371. [System.IO.File]::WriteAllText($this.getIcingaConfigFile(), $this.getProperty('old_icinga_config'));
  1372. $this.setProperty('require_restart', 'true');
  1373. }
  1374.  
  1375. #
  1376. # Provide a result of an operation (string) and
  1377. # the intended match value. In case every was
  1378. # ok, the function will return an info message
  1379. # with the result. Otherwise it will thrown an
  1380. # exception
  1381. #
  1382. $installer | Add-Member -membertype ScriptMethod -name 'printResultOkOrException' -value {
  1383. param([string]$result, [string]$expected);
  1384. if ($result -And $expected) {
  1385. if (-Not ($result -Like $expected)) {
  1386. throw $result;
  1387. } else {
  1388. $this.info($result);
  1389. }
  1390. } elseif ($result) {
  1391. $this.info($result);
  1392. }
  1393. }
  1394.  
  1395. #
  1396. # Generate the Icinga 2 SSL certificate to ensure the communication between the
  1397. # Agent and the Master can be established in first place
  1398. #
  1399. $installer | Add-Member -membertype ScriptMethod -name 'generateCertificates' -value {
  1400.  
  1401. if ($this.getProperty('local_hostname') -And $this.config('ca_server') -And $this.getProperty('icinga_ticket')) {
  1402. [string]$icingaPkiDir = Join-Path -Path $this.getProperty('config_dir') -ChildPath 'pki\';
  1403. [string]$icingaBinary = Join-Path -Path $this.getInstallPath() -ChildPath 'sbin\icinga2.exe';
  1404. [string]$agentName = $this.getProperty('local_hostname');
  1405.  
  1406. if (-Not (Test-Path $icingaBinary)) {
  1407. $this.warn('Unable to generate Icinga 2 certificates. Icinga 2 executable not found. It looks like the Icinga 2 Agent is not installed.');
  1408. return;
  1409. }
  1410.  
  1411. # Generate the certificate
  1412. $this.info('Generating Icinga 2 certificates');
  1413.  
  1414. $result = $this.startProcess($icingaBinary, $FALSE, 'pki new-cert --cn ' + $this.getProperty('local_hostname') + ' --key ' + $icingaPkiDir + $agentName + '.key --cert ' + $icingaPkiDir + $agentName + '.crt');
  1415. if ($result.Get_Item('exitcode') -ne 0) {
  1416. throw $result.Get_Item('message');
  1417. }
  1418. $this.info($result.Get_Item('message'));
  1419.  
  1420. # Save Certificate
  1421. $this.info("Storing Icinga 2 certificates");
  1422. $result = $this.startProcess($icingaBinary, $FALSE, 'pki save-cert --key ' + $icingaPkiDir + $agentName + '.key --trustedcert ' + $icingaPkiDir + 'trusted-master.crt --host ' + $this.config('ca_server'));
  1423. if ($result.Get_Item('exitcode') -ne 0) {
  1424. throw $result.Get_Item('message');
  1425. }
  1426. $this.info($result.Get_Item('message'));
  1427.  
  1428. # Validate if set against a given fingerprint for the CA
  1429. if (-Not $this.validateCertificate($icingaPkiDir + 'trusted-master.crt')) {
  1430. throw 'Failed to validate against CA authority';
  1431. }
  1432.  
  1433. # Request certificate
  1434. $this.info("Requesting Icinga 2 certificates");
  1435. $result = $this.startProcess($icingaBinary, $FALSE, 'pki request --host ' + $this.config('ca_server') + ' --port ' + $this.config('ca_port') + ' --ticket ' + $this.getProperty('icinga_ticket') + ' --key ' + $icingaPkiDir + $agentName + '.key --cert ' + $icingaPkiDir + $agentName + '.crt --trustedcert ' + $icingaPkiDir + 'trusted-master.crt --ca ' + $icingaPkiDir + 'ca.crt');
  1436. if ($result.Get_Item('exitcode') -ne 0) {
  1437. throw $result.Get_Item('message');
  1438. }
  1439. $this.info($result.Get_Item('message'));
  1440.  
  1441. $this.setProperty('require_restart', 'true');
  1442. } else {
  1443. $this.info('Skipping certificate generation. One or more of the following arguments is not set: -AgentName <name> -CAServer <server> -Ticket <ticket>');
  1444. }
  1445. }
  1446.  
  1447. #
  1448. # Validate against a given fingerprint if we are connected to the correct CA
  1449. #
  1450. $installer | Add-Member -membertype ScriptMethod -name 'validateCertificate' -value {
  1451. param([string] $certificate);
  1452.  
  1453. [System.Object]$certFingerprint = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2;
  1454. $certFingerprint.Import($certificate);
  1455. $this.info('Certificate fingerprint: ' + $certFingerprint.Thumbprint);
  1456.  
  1457. if ($this.config('ca_fingerprint')) {
  1458. if (-Not ($this.config('ca_fingerprint') -eq $certFingerprint.Thumbprint)) {
  1459. $this.error('CA fingerprint does not match! Expected: ' + $this.config('ca_fingerprint') + ', given: ' + $certFingerprint.Thumbprint);
  1460. return $FALSE;
  1461. } else {
  1462. $this.info('CA fingerprint validation successfull');
  1463. return $TRUE;
  1464. }
  1465. }
  1466.  
  1467. $this.warn('CA fingerprint validation disabled');
  1468. return $TRUE;
  1469. }
  1470.  
  1471. #
  1472. # Check the Icinga install directory and determine
  1473. # if the certificates are both available for the
  1474. # Agent. If not, return FALSE
  1475. #
  1476. $installer | Add-Member -membertype ScriptMethod -name 'hasCertificates' -value {
  1477. [string]$icingaPkiDir = Join-Path -Path $this.getProperty('config_dir') -ChildPath 'pki';
  1478. [string]$agentName = $this.getProperty('local_hostname');
  1479. if (
  1480. ((Test-Path ((Join-Path -Path $icingaPkiDir -ChildPath $agentName) + '.key'))) `
  1481. -And (Test-Path ((Join-Path -Path $icingaPkiDir -ChildPath $agentName) + '.crt')) `
  1482. -And (Test-Path (Join-Path -Path $icingaPkiDir -ChildPath 'ca.crt'))
  1483. ) {
  1484. return $TRUE;
  1485. }
  1486. return $FALSE;
  1487. }
  1488.  
  1489. #
  1490. # Have we passed an argument to force
  1491. # the creation of the certificates?
  1492. #
  1493. $installer | Add-Member -membertype ScriptMethod -name 'forceCertificateGeneration' -value {
  1494. return $this.config('force_cert');
  1495. }
  1496.  
  1497. #
  1498. # Is the current Agent the version
  1499. # we would like to install?
  1500. #
  1501. $installer | Add-Member -membertype ScriptMethod -name 'isAgentUpToDate' -value {
  1502. if ($this.canInstallAgent() -And $this.getProperty('agent_version') -eq $this.config('agent_version')) {
  1503. return $TRUE;
  1504. }
  1505.  
  1506. return $FALSE;
  1507. }
  1508.  
  1509. #
  1510. # Print a message telling us the installed
  1511. # and intended version of the Agent
  1512. #
  1513. $installer | Add-Member -membertype ScriptMethod -name 'printAgentUpdateMessage' -value {
  1514. $this.info('Current Icinga 2 Agent Version (' + $this.getProperty('agent_version') + ') is not matching intended version (' + $this.config('agent_version') + '). Downloading new version...');
  1515. }
  1516.  
  1517. #
  1518. # Do we allow Agent updates / downgrades?
  1519. #
  1520. $installer | Add-Member -membertype ScriptMethod -name 'allowAgentUpdates' -value {
  1521. return $this.config('allow_updates');
  1522. }
  1523.  
  1524. #
  1525. # Have we specified a version to install the Agent?
  1526. #
  1527. $installer | Add-Member -membertype ScriptMethod -name 'canInstallAgent' -value {
  1528. if ($this.config('download_url') -And $this.config('agent_version')) {
  1529. return $TRUE;
  1530. }
  1531.  
  1532. if (-Not $this.config('download_url') -And -Not $this.config('agent_version')) {
  1533. $this.warn('Icinga 2 Agent will not be installed. Arguments -DownloadUrl and -InstallAgentVersion both not defined.');
  1534. return $FALSE;
  1535. }
  1536.  
  1537. if (-Not $this.config('agent_version')) {
  1538. $this.warn('Icinga 2 Agent will not be installed. Argument -InstallAgentVersion is not defined.');
  1539. return $FALSE;
  1540. }
  1541.  
  1542. if (-Not $this.config('download_url')) {
  1543. $this.warn('Icinga 2 Agent will not be installed. Argument -DownloadUrl is not defined.');
  1544. return $FALSE;
  1545. }
  1546.  
  1547. return $FALSE;
  1548. }
  1549.  
  1550. #
  1551. # Check if all required arguments for writing a valid
  1552. # configuration are set
  1553. #
  1554. $installer | Add-Member -membertype ScriptMethod -name 'checkConfigInputParametersAndThrowException' -value {
  1555. if (-Not $this.getProperty('local_hostname')) {
  1556. throw 'Argument -AgentName <name> required for config generation.';
  1557. }
  1558. if (-Not $this.config('parent_zone')) {
  1559. throw 'Argument -ParentZone <name> required for config generation.';
  1560. }
  1561. if (-Not $this.getProperty('endpoint_nodes') -Or -Not $this.getProperty('endpoint_objects')) {
  1562. throw 'Argument -Endpoints <name> requires atleast one defined endpoint.';
  1563. }
  1564. }
  1565.  
  1566. #
  1567. # Execute a check with Icinga2 daemon -C
  1568. # to ensure the configuration is valid
  1569. #
  1570. $installer | Add-Member -membertype ScriptMethod -name 'isIcingaConfigValid' -value {
  1571. param([bool] $checkInternal = $TRUE);
  1572. if (-Not $this.config('parent_zone') -And $checkInternal) {
  1573. throw 'Parent Zone not defined. Please specify it with -ParentZone <name>';
  1574. }
  1575. $icingaBinary = Join-Path -Path $this.getInstallPath() -ChildPath 'sbin\icinga2.exe';
  1576.  
  1577. if (Test-Path $icingaBinary) {
  1578. $result = $this.startProcess($icingaBinary, $FALSE, 'daemon -C');
  1579. if ($result.Get_Item('exitcode') -ne 0) {
  1580. $this.error($result.Get_Item('message'));
  1581. return $FALSE;
  1582. }
  1583. } else {
  1584. $this.warn('Icinga 2 config validation not possible. Icinga 2 executable not found. Possibly the Agent is not installed.');
  1585. }
  1586. return $TRUE;
  1587. }
  1588.  
  1589. #
  1590. # Returns true or false, depending
  1591. # if any changes were made requiring
  1592. # the Icinga 2 Agent to become restarted
  1593. #
  1594. $installer | Add-Member -membertype ScriptMethod -name 'madeChanges' -value {
  1595. return $this.getProperty('require_restart');
  1596. }
  1597.  
  1598. #
  1599. # Apply possible configuration changes to
  1600. # our Icinga 2 Agent
  1601. #
  1602. $installer | Add-Member -membertype ScriptMethod -name 'applyPossibleConfigChanges' -value {
  1603. if ($this.hasConfigChanged() -And $this.getProperty('generate_config') -eq 'true') {
  1604. $this.backupDefaultConfig();
  1605. $this.writeConfig($this.getProperty('new_icinga_config'));
  1606.  
  1607. # Check if the config is valid and rollback otherwise
  1608. if (-Not $this.isIcingaConfigValid()) {
  1609. $this.error('Icinga 2 config validation failed. Rolling back to previous version.');
  1610. if (-Not $this.hasCertificates()) {
  1611. $this.error('Icinga 2 certificates not found. Please generate the certificates over this module or add them manually.');
  1612. }
  1613. $this.rollbackConfig();
  1614. if ($this.isIcingaConfigValid($FALSE)) {
  1615. $this.info('Rollback of Icinga 2 configuration successfull.');
  1616. } else {
  1617. throw 'Icinga 2 config rollback failed. Please check the icinga2.log';
  1618. }
  1619. } else {
  1620. $this.info('Icinga 2 configuration check successfull.');
  1621. }
  1622. } else {
  1623. $this.info('icinga2.conf did not change or required parameters not set. Nothing to do');
  1624. }
  1625. }
  1626.  
  1627. #
  1628. # Enable or disable the Icinga 2 debug log
  1629. #
  1630. $installer | Add-Member -membertype ScriptMethod -name 'switchIcingaDebugLog' -value {
  1631. # In case the config is not valid -> do nothing
  1632. if (-Not $this.isIcingaConfigValid($FALSE)) {
  1633. throw 'Unable to process Icinga 2 debug configuration. The icinga2.conf is corrupt! Please check the icinga2.log';
  1634. }
  1635.  
  1636. # If there is no config file defined -> do nothing
  1637. if (-Not (Test-Path $this.getIcingaConfigFile())) {
  1638. return;
  1639. }
  1640.  
  1641. [string]$icingaCurrentConfig = [System.IO.File]::ReadAllText($this.getIcingaConfigFile());
  1642. [string]$newIcingaConfig = '';
  1643.  
  1644. if ($this.config('icinga_enable_debug_log')) {
  1645. $this.info('Trying to enable debug log for Icinga 2...');
  1646. if ($icingaCurrentConfig.Contains('const PowerShellIcinga2EnableDebug = false;')) {
  1647. $newIcingaConfig = $icingaCurrentConfig.Replace('const PowerShellIcinga2EnableDebug = false;', 'const PowerShellIcinga2EnableDebug = true;');
  1648. $this.info('Icinga 2 debug log has been enabled');
  1649. } else {
  1650. $this.info('Icinga 2 debug log is already enabled or configuration not found');
  1651. }
  1652. } else {
  1653. $this.info('Trying to disable debug log for Icinga 2...');
  1654. if ($icingaCurrentConfig.Contains('const PowerShellIcinga2EnableDebug = true;')) {
  1655. $newIcingaConfig = $icingaCurrentConfig.Replace('const PowerShellIcinga2EnableDebug = true;', 'const PowerShellIcinga2EnableDebug = false;');
  1656. $this.info('Icinga 2 debug log has been disabled');
  1657. } else {
  1658. $this.info('Icinga 2 debug log is not enabled or configuration not found');
  1659. }
  1660. }
  1661.  
  1662. # In case we made a modification to the configuration -> write it
  1663. if ($newIcingaConfig -ne '') {
  1664. $this.writeConfig($newIcingaConfig);
  1665. # Validate the config if it is valid
  1666. if (-Not $this.isIcingaConfigValid($FALSE)) {
  1667. # if not write the old configuration again
  1668. $this.writeConfig($icingaCurrentConfig);
  1669. if (-Not $this.isIcingaConfigValid($FALSE)) {
  1670. throw 'Critical exception: Something went wrong while processing debug configuration. The Icinga 2 config is corrupt! Please check the icinga2.log';
  1671. }
  1672. }
  1673. }
  1674. }
  1675.  
  1676. #
  1677. # Ensure we get the hostname or FQDN
  1678. # from the PowerShell to make things more
  1679. # easier
  1680. #
  1681. $installer | Add-Member -membertype ScriptMethod -name 'fetchHostnameOrFQDN' -value {
  1682. if ($this.config('fetch_agent_fqdn') -And (Get-WmiObject win32_computersystem).Domain) {
  1683. [string]$hostname = (Get-WmiObject win32_computersystem).DNSHostName + '.' + (Get-WmiObject win32_computersystem).Domain;
  1684. $this.setProperty('local_hostname', $hostname);
  1685. $this.info('Setting internal Agent Name to ' + $this.getProperty('local_hostname'));
  1686. } elseif ($this.config('fetch_agent_name')) {
  1687. [string]$hostname = (Get-WmiObject win32_computersystem).DNSHostName;
  1688. $this.setProperty('local_hostname', $hostname);
  1689. $this.info('Setting internal Agent Name to ' + $this.getProperty('local_hostname'));
  1690. }
  1691.  
  1692. # Add additional variables to our config for more user-friendly usage
  1693. [string]$host_fqdn = (Get-WmiObject win32_computersystem).DNSHostName + '.' + (Get-WmiObject win32_computersystem).Domain;
  1694. [string]$hostname = (Get-WmiObject win32_computersystem).DNSHostName;
  1695.  
  1696. $this.setProperty('fqdn', $host_fqdn);
  1697. $this.setProperty('hostname', $hostname);
  1698.  
  1699. if (-Not $this.getProperty('local_hostname')) {
  1700. $this.warn('You have not specified an Agent Name or turned on to auto fetch this information.');
  1701. }
  1702. }
  1703.  
  1704. #
  1705. # Retreive the current IP-Address of the Host
  1706. #
  1707. $installer | Add-Member -membertype ScriptMethod -name 'fetchHostIPAddress' -value {
  1708.  
  1709. # First try to lookup the IP by the FQDN
  1710. if ($this.doLookupIPAddressesForHostname($this.getProperty('fqdn'))) {
  1711. return;
  1712. }
  1713.  
  1714. # Now take a look for the given hostname
  1715. if ($this.doLookupIPAddressesForHostname($this.getProperty('hostname'))) {
  1716. return;
  1717. }
  1718.  
  1719. # If still nothing is found, look on the entire host
  1720. if ($this.doLookupIPAddressesForHostname("")) {
  1721. return;
  1722. }
  1723. }
  1724.  
  1725. #
  1726. # Add all found IP-Addresses to our property array
  1727. #
  1728. $installer | Add-Member -membertype ScriptMethod -name 'doLookupIPAddressesForHostname' -value {
  1729. param([string]$hostname);
  1730.  
  1731. $this.info('Trying to fetch Host IP-Address for hostname: ' + $hostname);
  1732. try {
  1733. [array]$ipAddressArray = [Net.DNS]::GetHostEntry($hostname).AddressList;
  1734. $this.addHostIPAddressToProperties($ipAddressArray);
  1735. return $TRUE;
  1736. } catch {
  1737. # Write an error in case something went wrong
  1738. $this.error('Failed to lookup IP-Address with DNS-Lookup for ' + $hostname + ': ' + $_.Exception.Message);
  1739. }
  1740. return $FALSE;
  1741. }
  1742.  
  1743. #
  1744. # Add all found IP-Addresses to our property array
  1745. #
  1746. $installer | Add-Member -membertype ScriptMethod -name 'addHostIPAddressToProperties' -value {
  1747. param($ipArray);
  1748.  
  1749. [int]$ipV4Index = 0;
  1750. [int]$ipV6Index = 0;
  1751.  
  1752. foreach ($address in $ipArray) {
  1753. # Split config attributes for IPv4 and IPv6 into different values
  1754. if ($address.AddressFamily -eq 'InterNetwork') { #IPv4
  1755. # If the first entry of our default ipaddress is empty -> add it
  1756. if ($this.getProperty('ipaddress') -eq $null) {
  1757. $this.setProperty('ipaddress', $address);
  1758. }
  1759. # Now add the IP's with an array like construct
  1760. $this.setProperty('ipaddress[' + $ipV4Index + ']', $address);
  1761. $ipV4Index += 1;
  1762. } else { #IPv6
  1763. # If the first entry of our default ipaddress is empty -> add it
  1764. if ($this.getProperty('ipaddressV6') -eq $null) {
  1765. $this.setProperty('ipaddressV6', $address);
  1766. }
  1767. # Now add the IP's with an array like construct
  1768. $this.setProperty('ipaddressV6[' + $ipV6Index + ']', $address);
  1769. $ipV6Index += 1;
  1770. }
  1771. }
  1772. }
  1773.  
  1774. #
  1775. # Transform the hostname to upper or lower case if required
  1776. # 0: Do nothing (default)
  1777. # 1: Transform to lower case
  1778. # 2: Transform to upper case
  1779. #
  1780. $installer | Add-Member -MemberType ScriptMethod -name 'doTransformHostname' -Value {
  1781. [string]$hostname = $this.getProperty('local_hostname');
  1782. [int]$type = $this.config('transform_hostname');
  1783. switch ($type) {
  1784. 1 { $hostname = $hostname.ToLower(); }
  1785. 2 { $hostname = $hostname.ToUpper(); }
  1786. Default {} # Do nothing by default
  1787. }
  1788.  
  1789. if ($hostname -cne $this.getProperty('local_hostname')) {
  1790. $this.info('Transforming Agent Name to ' + $hostname);
  1791. }
  1792.  
  1793. $this.setProperty('local_hostname', $hostname);
  1794. }
  1795.  
  1796. #
  1797. # Allow the replacing of placeholders within a JSON-String
  1798. #
  1799. $installer | Add-Member -MemberType ScriptMethod -name 'doReplaceJSONPlaceholders' -Value {
  1800. param([string]$jsonString);
  1801.  
  1802. # Replace the encoded & with the original symbol at first
  1803. $jsonString = $jsonString.Replace('\u0026', '&');
  1804.  
  1805. # &hostname& => hostname
  1806. $jsonString = $jsonString.Replace('&hostname&', $this.getProperty('hostname'));
  1807.  
  1808. # &hostname.lowerCase& => hostname to lower
  1809. $jsonString = $jsonString.Replace('&hostname.lowerCase&', $this.getProperty('hostname').ToLower());
  1810.  
  1811. # &hostname.upperCase& => hostname to upper
  1812. $jsonString = $jsonString.Replace('&hostname.upperCase&', $this.getProperty('hostname').ToUpper());
  1813.  
  1814. # &fqdn& => fqdn
  1815. $jsonString = $jsonString.Replace('&fqdn&', $this.getProperty('fqdn'));
  1816.  
  1817. # &fqdn.lowerCase& => fqdn to lower
  1818. $jsonString = $jsonString.Replace('&fqdn.lowerCase&', $this.getProperty('fqdn').ToLower());
  1819.  
  1820. # &fqdn.upperCase& => fqdn to upper
  1821. $jsonString = $jsonString.Replace('&fqdn.upperCase&', $this.getProperty('fqdn').ToUpper());
  1822.  
  1823. # hostname_placeholder => current hostname (either FQDN, hostname, with plain, upper or lower case)
  1824. $jsonString = $jsonString.Replace('&hostname_placeholder&', $this.getProperty('local_hostname'));
  1825.  
  1826. # Try to replace our IP-Address
  1827. if ($jsonString.Contains('&ipaddressV6')) {
  1828. $jsonString = $this.doReplaceJSONIPAddress($jsonString, 'ipaddressV6');
  1829. } elseif ($jsonString.Contains('&ipaddress')) {
  1830. $jsonString = $this.doReplaceJSONIPAddress($jsonString, 'ipaddress');
  1831. }
  1832.  
  1833. # Encode the & again to receive a proper JSON
  1834. $jsonString = $jsonString.Replace('&', '\u0026');
  1835.  
  1836. return $jsonString;
  1837. }
  1838.  
  1839. #
  1840. # Allow the replacing of added IPv4 and IPv6 address
  1841. #
  1842. $installer | Add-Member -MemberType ScriptMethod -name 'doReplaceJSONIPAddress' -Value {
  1843. param([string]$jsonString, [string]$ipType);
  1844.  
  1845. # Add our & delimeter to begin with
  1846. [string]$ipSearchPattern = '&' + $ipType;
  1847.  
  1848. # Now locate the string and cut everything away until only our & tag for the string shall be remaining, including the array placeholder
  1849. [string]$ipAddressEnd = $jsonString.Substring($jsonString.IndexOf($ipSearchPattern) + $ipType.Length + 1, $jsonString.Length - $jsonString.IndexOf($ipSearchPattern) - $ipType.Length - 1);
  1850. # Ensure we still got an ending &, otherwise throw an error
  1851. if ($ipAddressEnd.Contains('&')) {
  1852. # Now cut everything until the first & we found
  1853. $ipAddressEnd = $ipAddressEnd.Substring(0, $ipAddressEnd.IndexOf('&'));
  1854. # Build together our IP-Address string, which could be for example ipaddress[1]
  1855. [string]$ipAddressString = $ipType + $ipAddressEnd;
  1856.  
  1857. # Now replace this finding with our config attribute
  1858. $jsonString = $jsonString.Replace('&' + $ipAddressString + '&', $this.getProperty($ipAddressString));
  1859. } else {
  1860. # If something goes wrong we require to notify our user
  1861. $this.error('Failed to replace IP-Address placeholder. Invalid format for IP-Type ' + $ipType);
  1862. }
  1863.  
  1864. # Return our new JSON-String
  1865. return $jsonString;
  1866. }
  1867.  
  1868. #
  1869. # Check if the local host key is still valid
  1870. #
  1871. $installer | Add-Member -membertype ScriptMethod -name 'isHostAPIKeyValid' -value {
  1872.  
  1873. # If no API key is yet defined, we will require to fetch one
  1874. if (-Not $this.getProperty('director_host_token')) {
  1875. return $FALSE;
  1876. }
  1877.  
  1878. # Check against the powershell-parameter URL if our host API key is valid
  1879. # If we receive content -> everything is ok
  1880. # If we receive any 4xx code, propably the API Key is invalid and we require to fetch a new one
  1881. [string]$url = $this.config('director_url') + 'self-service/powershell-parameters?key=' + $this.getProperty('director_host_token');
  1882. [string]$response = $this.createHTTPRequest($url, '', 'POST', 'application/json', $TRUE, $FALSE);
  1883. if ($this.isHTTPResponseCode($response)) {
  1884. if ($response[0] -eq '4') {
  1885. $this.info('Target host is already present inside Icinga Director without API-Key. Re-Creating key...');
  1886. return $FALSE;
  1887. }
  1888. }
  1889.  
  1890. $this.info('Host API-Key validation successfull.');
  1891. return $TRUE;
  1892. }
  1893.  
  1894. #
  1895. # This function will allow us to create a
  1896. # host object directly inside the Icinga Director
  1897. # with a provided JSON string
  1898. #
  1899. $installer | Add-Member -membertype ScriptMethod -name 'createHostInsideIcingaDirector' -value {
  1900.  
  1901. if ($this.config('director_url') -And $this.getProperty('local_hostname')) {
  1902. if ($this.config('director_auth_token')) {
  1903. if ($this.requireIcingaDirectorAPIVersion('1.4.0', '[Function::createHostInsideIcingaDirector]')) {
  1904.  
  1905. # Check if our API Host-Key is present and valid
  1906. if ($this.isHostAPIKeyValid()) {
  1907. return;
  1908. }
  1909.  
  1910. # If not, try to create the host and fetch the API key
  1911. [string]$apiKey = $this.config('director_auth_token');
  1912. [string]$url = $this.config('director_url') + 'self-service/register-host?name=' + $this.getProperty('local_hostname') + '&key=' + $apiKey;
  1913. [string]$json = '';
  1914. # If no JSON Object is defined (should be default), we shall create one
  1915. if (-Not $this.config('director_host_object')) {
  1916. [string]$hostname = $this.getProperty('local_hostname');
  1917. $json = '{ "address": "' + $hostname + '", "display_name": "' + $hostname + '" }';
  1918. } else {
  1919. # Otherwise use the specified one and replace the host object placeholders
  1920. $json = $this.doReplaceJSONPlaceholders($this.config('director_host_object'));
  1921. }
  1922.  
  1923. $this.info('Creating host ' + $this.getProperty('local_hostname') + ' over API token inside Icinga Director.');
  1924.  
  1925. [string]$httpResponse = $this.createHTTPRequest($url, $json, 'POST', 'application/json', $TRUE, $TRUE);
  1926.  
  1927. if ($this.isHTTPResponseCode($httpResponse) -eq $FALSE) {
  1928. $this.setProperty('director_host_token', $httpResponse);
  1929. $this.writeHostAPIKeyToDisk();
  1930. } else {
  1931. if ($httpResponse -eq '400') {
  1932. $this.warn('Received response 400 from Icinga Director. Possibly you tried to re-create the host ' + $this.getProperty('local_hostname') + '. In case the host already exists, please remove the Host-Api-Key inside the Icinga Director and try again.');
  1933. } else {
  1934. $this.warn('Failed to create host. Response code ' + $httpResponse);
  1935. }
  1936. }
  1937. }
  1938. } elseif ($this.config('director_host_object')) {
  1939. # Setup the url we need to call
  1940. [string]$url = $this.config('director_url') + 'host';
  1941. # Replace the host object placeholders
  1942. [string]$host_object_json = $this.doReplaceJSONPlaceholders($this.config('director_host_object'));
  1943. # Create the host object inside the director
  1944. [string]$httpResponse = $this.createHTTPRequest($url, $host_object_json, 'PUT', 'application/json', $FALSE, $FALSE);
  1945.  
  1946. if ($this.isHTTPResponseCode($httpResponse) -eq $FALSE) {
  1947. $this.info('Placed query for creating host ' + $this.getProperty('local_hostname') + ' inside Icinga Director. Config: ' + $httpResponse);
  1948. } else {
  1949. if ($httpResponse -eq '422') {
  1950. $this.warn('Failed to create host ' + $this.getProperty('local_hostname') + ' inside Icinga Director. The host seems to already exist.');
  1951. } else {
  1952. $this.error('Failed to create host ' + $this.getProperty('local_hostname') + ' inside Icinga Director. Error response ' + $httpResponse);
  1953. }
  1954. }
  1955. # Shall we deploy the config for the generated host?
  1956. if ($this.config('director_deploy_config')) {
  1957. $url = $this.config('director_url') + 'config/deploy';
  1958. [string]$httpResponse = $this.createHTTPRequest($url, '', 'POST', 'application/json', $FALSE, $TRUE);
  1959. $this.info('Deploying configuration from Icinga Director to Icinga. Result: ' + $httpResponse);
  1960. }
  1961. }
  1962. }
  1963. }
  1964.  
  1965. #
  1966. # Write Host API-Key for future usage
  1967. #
  1968. $installer | Add-Member -membertype ScriptMethod -name 'writeHostAPIKeyToDisk' -value {
  1969. if (Test-Path ($this.getProperty('config_dir'))) {
  1970. [string]$apiFile = Join-Path -Path $this.getProperty('config_dir') -ChildPath 'icingadirector.token';
  1971. $this.info('Writing host API-Key "' + $this.getProperty('director_host_token') + '" to "' + $apiFile + '"');
  1972. [System.IO.File]::WriteAllText($apiFile, $this.getProperty('director_host_token'));
  1973. }
  1974. }
  1975.  
  1976. #
  1977. # Read Host API-Key from disk for usage
  1978. #
  1979. $installer | Add-Member -membertype ScriptMethod -name 'readHostAPIKeyFromDisk' -value {
  1980. [string]$apiFile = Join-Path -Path $this.getProperty('config_dir') -ChildPath 'icingadirector.token';
  1981. if (Test-Path ($apiFile)) {
  1982. [string]$hostToken = [System.IO.File]::ReadAllText($apiFile);
  1983. $this.setProperty('director_host_token', $hostToken);
  1984. $this.info('Reading host api token ' + $hostToken + ' from ' + $apiFile);
  1985. } else {
  1986. $this.setProperty('director_host_token', '');
  1987. }
  1988. }
  1989.  
  1990. #
  1991. # Get the API Version from the Icinga Director. In case we are using
  1992. # an older Version of the Director, we wont get this version
  1993. #
  1994. $installer | Add-Member -membertype ScriptMethod -name 'getIcingaDirectorVersion' -value {
  1995. if ($this.config('director_url')) {
  1996. # Do a legacy call to the Icinga Director and get a JSON-Value
  1997. # Older versions of the Director do not support plain/text and
  1998. # would result in making this request quite useless
  1999.  
  2000. [string]$url = $this.config('director_url') + 'self-service/api-version';
  2001. [string]$versionString = $this.createHTTPRequest($url, '', 'POST', 'application/json', $FALSE, $FALSE);
  2002.  
  2003. if ($this.isHTTPResponseCode($versionString) -eq $FALSE) {
  2004. # Remove all characters we do not need inside the string
  2005. [string]$versionString = $versionString.Replace('"', '').Replace("`r", '').Replace("`n", '');
  2006. [array]$version = $versionString.Split('.');
  2007. $this.setProperty('icinga_director_api_version', $versionString);
  2008. return;
  2009. } else {
  2010. $this.warn('You seem to use an older Version of the Icinga Director, as no API version could be retreived.');
  2011. $this.setProperty('icinga_director_api_version', '0.0.0');
  2012. return;
  2013. }
  2014. }
  2015. $this.setProperty('icinga_director_api_version', 'false');
  2016. }
  2017.  
  2018. #
  2019. # Match the Icinga Director API Version against a provided string
  2020. #
  2021. $installer | Add-Member -membertype ScriptMethod -name 'requireIcingaDirectorAPIVersion' -value {
  2022. param([string]$version, [string]$functionName);
  2023.  
  2024. # Director URL not specified
  2025. if ($this.getProperty('icinga_director_api_version') -eq 'false') {
  2026. return $FALSE;
  2027. }
  2028.  
  2029. if ($this.getProperty('icinga_director_api_version') -eq '0.0.0') {
  2030. $this.error('The feature ' + $functionName + ' requires Icinga Director API-Version ' + $version + '. Your Icinga Director version does not support the API.');
  2031. return $FALSE;
  2032. }
  2033.  
  2034. [bool]$versionValid = $TRUE;
  2035. [array]$requiredVersion = $version.Split('.');
  2036. $currentVersion = $this.getProperty('icinga_director_api_version');
  2037.  
  2038. if ($requiredVersion[0] -gt $currentVersion[0]) {
  2039. $versionValid = $FALSE;
  2040. }
  2041.  
  2042. if ($requiredVersion[1] -gt $currentVersion[2]) {
  2043. $versionValid = $FALSE;
  2044. }
  2045.  
  2046. if ($requiredVersion[1] -ge $currentVersion[2] -And $requiredVersion[2] -gt $currentVersion[4]) {
  2047. $versionValid = $FALSE;
  2048. }
  2049.  
  2050. if ($versionValid -eq $FALSE) {
  2051. $this.error('The feature ' + $functionName + ' requires Icinga Director API-Version ' + $version + '. Got version ' + $currentVersion[0] + '.' + $currentVersion[2] + '.' + $currentVersion[4]);
  2052. return $FALSE;
  2053. }
  2054.  
  2055. return $TRUE;
  2056. }
  2057.  
  2058. #
  2059. # This function will convert a [hashtable] or [array] object to string
  2060. # with function ConvertTo-Json for argument -DirectorHostObject.
  2061. # It will however only process those if the PowerShell Version is 3
  2062. # and above, because Version 2 is not providing the required
  2063. # functionality. In that case the module will throw an exception
  2064. #
  2065. $installer | Add-Member -membertype ScriptMethod -name 'convertDirectorHostObjectArgument' -value {
  2066.  
  2067. # First add the value to an object we can work with
  2068. [System.Object]$json = $this.config('director_host_object');
  2069.  
  2070. # Prevent processing of empty data
  2071. if ($json -eq $null -Or $json -eq '') {
  2072. return;
  2073. }
  2074.  
  2075. # In case the argument is already a string -> nothing to do
  2076. if ($json.GetType() -eq [string]) {
  2077. # Do nothing
  2078. return;
  2079. } elseif ($json.GetType() -eq [hashtable] -Or $json.GetType() -eq [array]) {
  2080. # Check which PowerShell Version we are using and throw an error in case our Version does not support the argument
  2081. if ($PSVersionTable.PSVersion.Major -lt 3) {
  2082. [string]$errorMessage = 'You are trying to pass an object of type [hashtable] or [array] for argument "-DirectorHostObject", but are using ' +
  2083. 'PowerShell Version 2 or lower. Passing hashtables through this argument is possible, but it requires to be ' +
  2084. 'converted with function ConvertTo-Json, which is available on PowerShell Version 3 and above only. ' +
  2085. 'You can still process JSON-Values with this module, even on PowerShell Version 2, but you will have to pass the ' +
  2086. 'JSON as string instead of an object. This module will now exit with an error code. For further details, please ' +
  2087. 'read the documentation for the "-DirectorHostObject" argument. ' +
  2088. 'Documentation: https://github.com/Icinga/icinga2-powershell-module/blob/master/doc/10-Basic-Arguments.md';
  2089. $this.exception($errorMessage);
  2090. throw 'PowerShell Version exception.';
  2091. }
  2092.  
  2093. # If our PowerShell Version is supporting the function, convert it to a valid string
  2094. $this.overrideConfig('director_host_object', (ConvertTo-Json -Compress $json));
  2095. }
  2096. }
  2097.  
  2098. #
  2099. # This function will fetch all arguments configured inside the Icinga Director
  2100. # to allow an entire auto configuration of the Icinga 2 Agent
  2101. #
  2102. $installer | Add-Member -membertype ScriptMethod -name 'fetchArgumentsFromIcingaDirector' -value {
  2103. param([bool]$globalConfig);
  2104.  
  2105. # By default we will use the Host-Api-Key stored on the disk (if written on)
  2106. [string]$key = $this.getProperty('director_host_token');
  2107.  
  2108. # In case we are not having the Host-Api-Key already, use the value from the argument
  2109. if($globalConfig -eq $TRUE) {
  2110. $key = $this.config('director_auth_token');
  2111. }
  2112.  
  2113. # If no key is specified, we are not having set one and should leave this function
  2114. if ($key -eq '') {
  2115. return;
  2116. }
  2117.  
  2118. if ($this.requireIcingaDirectorAPIVersion('1.4.0', '[Function::fetchArgumentsFromIcingaDirector]')) {
  2119. [string]$url = $this.config('director_url') + 'self-service/powershell-parameters?key=' + $key;
  2120. [string]$argumentString = $this.createHTTPRequest($url, '', 'POST', 'application/json', $TRUE, $FALSE);
  2121.  
  2122. if ($this.isHTTPResponseCode($argumentString) -eq $FALSE) {
  2123. # First split the entire result based in new-lines into an array
  2124. [array]$arguments = $argumentString.Split("`n");
  2125. $config = @{};
  2126.  
  2127. # Now loop all elements and construct a dictionary for all values
  2128. foreach ($item in $arguments) {
  2129. if ($item.Contains(':')) {
  2130. [int]$argumentPos = $item.IndexOf(":");
  2131. [string]$argument = $item.Substring(0, $argumentPos)
  2132. [string]$value = $item.Substring($argumentPos + 2, $item.Length - 2 - $argumentPos);
  2133. $value = $value.Replace("`r", '');
  2134. $value = $value.Replace("`n", '');
  2135.  
  2136. if ($value.Contains( '!')) {
  2137. [array]$valueArray = $value.Split('!');
  2138. $this.overrideConfig($argument, $valueArray);
  2139. } else {
  2140. if ($value.toLower() -eq 'true') {
  2141. $this.overrideConfig($argument, $TRUE);
  2142. } elseif ($value.toLower() -eq 'false') {
  2143. $this.overrideConfig($argument, $FALSE);
  2144. } else {
  2145. $this.overrideConfig($argument, $value);
  2146. }
  2147. }
  2148. }
  2149. }
  2150. } else {
  2151. $this.error('Received ' + $argumentString + ' from Icinga Director. Possibly your API token is no longer valid or the object does not exist.');
  2152. }
  2153. # Ensure we generate the required configuration content
  2154. $this.generateConfigContent();
  2155. }
  2156. }
  2157.  
  2158. #
  2159. # This function will communicate directly with
  2160. # the Icinga Director and ensuring that we get
  2161. # some of the possible required informations
  2162. #
  2163. $installer | Add-Member -membertype ScriptMethod -name 'fetchTicketFromIcingaDirector' -value {
  2164.  
  2165. if ($this.getProperty('director_host_token')) {
  2166. if ($this.requireIcingaDirectorAPIVersion('1.4.0', '[Function::fetchTicketFromIcingaDirector]')) {
  2167. [string]$url = $this.config('director_url') + 'self-service/ticket?key=' + $this.getProperty('director_host_token');
  2168. [string]$httpResponse = $this.createHTTPRequest($url, '', 'POST', 'application/json', $TRUE, $TRUE);
  2169. if ($this.isHTTPResponseCode($httpResponse) -eq $FALSE) {
  2170. $this.setProperty('icinga_ticket', $httpResponse);
  2171. } else {
  2172. $this.error('Failed to fetch Ticket from Icinga Director. Error response ' + $httpResponse);
  2173. }
  2174. }
  2175. } else {
  2176. if ($this.config('director_url') -And $this.getProperty('local_hostname')) {
  2177. [string]$url = $this.config('director_url') + 'host/ticket?name=' + $this.getProperty('local_hostname');
  2178. [string]$httpResponse = $this.createHTTPRequest($url, '', 'POST', 'application/json', $FALSE, $TRUE);
  2179.  
  2180. if ($this.isHTTPResponseCode($httpResponse) -eq $FALSE) {
  2181. # Lookup all " inside the return string
  2182. $quotes = Select-String -InputObject $httpResponse -Pattern '"' -AllMatches;
  2183.  
  2184. # If we only got two ", we should have received a valid ticket
  2185. # Otherwise we need to throw an error
  2186. if ($quotes.Matches.Count -ne 2) {
  2187. throw 'Failed to fetch ticket for host ' + $this.getProperty('local_hostname') +'. Got ' + $httpResponse + ' as ticket.';
  2188. } else {
  2189. $httpResponse = $httpResponse.subString(1, $httpResponse.length - 3);
  2190. $this.info('Fetched ticket ' + $httpResponse + ' for host ' + $this.getProperty('local_hostname') + '.');
  2191. $this.setProperty('icinga_ticket', $httpResponse);
  2192. }
  2193. } else {
  2194. $this.error('Failed to fetch Ticket from Icinga Director. Error response ' + $httpResponse);
  2195. }
  2196. }
  2197. }
  2198. }
  2199.  
  2200. #
  2201. # Shall we install the NSClient as well on the system?
  2202. # All possible actions are handeled here
  2203. #
  2204. $installer | Add-Member -membertype ScriptMethod -name 'installNSClient' -value {
  2205.  
  2206. if ($this.config('install_nsclient')) {
  2207.  
  2208. [string]$installerPath = $this.getNSClientInstallerPath();
  2209. $this.info('Trying to install NSClient++ from ' + $installerPath);
  2210.  
  2211. # First check if the package does exist
  2212. if (Test-Path ($installerPath)) {
  2213.  
  2214. # Get all required arguments for installing the NSClient unattended
  2215. [string]$NSClientArguments = $this.getNSClientInstallerArguments();
  2216.  
  2217. # Start the installer process
  2218. $result = $this.startProcess('MsiExec.exe', $TRUE, '/quiet /i "' + $installerPath + '" ' + $NSClientArguments);
  2219.  
  2220. # Exit Code 0 means the NSClient was installed successfully
  2221. # Otherwise we require to throw an error
  2222. if ($result.Get_Item('exitcode') -ne 0) {
  2223. $this.exception('Failed to install NSClient++. ' + $result.Get_Item('message'));
  2224. } else {
  2225. $this.info('NSClient++ successfully installed.');
  2226. }
  2227.  
  2228. # If defined remove the Firewall Rule to secure the system
  2229. # By default the NSClient is only called from the Icinga 2 Agent locally
  2230. $this.removeNSClientFirewallRule();
  2231. # Remove the service if we only call the NSClient locally
  2232. $this.removeNSClientService();
  2233. # Add the default NSClient config if we want to do more
  2234. $this.addNSClientDefaultConfig();
  2235. # To tell Icinga 2 we installed the NSClient and to make
  2236. # the NSCPPath variable available, we require to restart Icinga 2
  2237. $this.setProperty('require_restart', 'true');
  2238. } else {
  2239. $this.error('Failed to locate NSClient++ Installer at ' + $installerPath);
  2240. }
  2241. } else {
  2242. $this.info('NSClient++ will not be installed on the system.');
  2243. }
  2244. }
  2245.  
  2246. #
  2247. # Determine the location of the NSClient installer
  2248. # By default we are using the shipped NSClient from the Icinga 2 Agent
  2249. #
  2250. $installer | Add-Member -membertype ScriptMethod -name 'getNSClientInstallerPath' -value {
  2251.  
  2252. if ($this.config('nsclient_installer_path') -ne '') {
  2253.  
  2254. # Check of the installer is a local path
  2255. # If so, use this as installer source
  2256. if (Test-Path ($this.config('nsclient_installer_path'))) {
  2257. return $this.config('nsclient_installer_path');
  2258. }
  2259.  
  2260. $this.info('Trying to download NSClient++ from ' + $this.config('nsclient_installer_path'));
  2261. [System.Object]$client = New-Object System.Net.WebClient;
  2262. $client.DownloadFile($this.config('nsclient_installer_path'), (Join-Path -Path $Env:temp -ChildPath 'NSCP.msi'));
  2263.  
  2264. return (Join-Path -Path $Env:temp -ChildPath 'NSCP.msi');
  2265. } else {
  2266. # Icinga is shipping a NSClient Version after installation
  2267. # Install this version if defined
  2268. return (Join-Path -Path $this.getInstallPath() -ChildPath 'sbin\NSCP.msi');
  2269. }
  2270.  
  2271. return '';
  2272. }
  2273.  
  2274. #
  2275. # If we only want to use the NSClient++ to be called from the Icinga 2 Agent
  2276. # we do not require an open Firewall Rule to allow traffic.
  2277. #
  2278. $installer | Add-Member -membertype ScriptMethod -name 'getNSClientInstallerArguments' -value {
  2279. [string]$NSClientArguments = '';
  2280.  
  2281. if ($this.config('nsclient_directory')) {
  2282. $NSClientArguments += ' INSTALLLOCATION=' + $this.config('nsclient_directory');
  2283. }
  2284.  
  2285. return $NSClientArguments;
  2286. }
  2287.  
  2288. #
  2289. # If we only want to use the NSClient++ to be called from the Icinga 2 Agent
  2290. # we do not require an open Firewall Rule to allow traffic.
  2291. #
  2292. $installer | Add-Member -membertype ScriptMethod -name 'removeNSClientFirewallRule' -value {
  2293. if ($this.config('nsclient_firewall') -eq $FALSE) {
  2294.  
  2295. $result = $this.startProcess('netsh', $FALSE, 'advfirewall firewall show rule name="NSClient++ Monitoring Agent"');
  2296. if ($result.Get_Item('exitcode') -ne 0) {
  2297. # Firewall rule was not found. Nothing to do
  2298. $this.info('NSClient++ Firewall Rule is not installed');
  2299. return;
  2300. }
  2301.  
  2302. $this.info('Trying to remove NSClient++ Firewall Rule...');
  2303.  
  2304. $result = $this.startProcess('netsh', $TRUE, 'advfirewall firewall delete rule name="NSClient++ Monitoring Agent"');
  2305.  
  2306. if ($result.Get_Item('exitcode') -ne 0) {
  2307. $this.error('Failed to remove NSClient++ Firewall rule: ' + $result.Get_Item('message'));
  2308. } else {
  2309. $this.info('NSClient++ Firewall Rule has been successfully removed');
  2310. }
  2311. }
  2312. }
  2313.  
  2314. #
  2315. # If we only want to use the NSClient++ to be called from the Icinga 2 Agent
  2316. # we do not require a running NSClient++ Service
  2317. #
  2318. $installer | Add-Member -membertype ScriptMethod -name 'removeNSClientService' -value {
  2319. if ($this.config('nsclient_service') -eq $FALSE) {
  2320. $NSClientService = Get-WmiObject -Class Win32_Service -Filter "Name='nscp'";
  2321. if ($NSClientService -ne $null) {
  2322. $this.info('Trying to remove NSClient++ service...');
  2323. # Before we remove the service, stop it (to prevent ghosts)
  2324. Stop-Service 'nscp';
  2325. # Now remove it
  2326. $result = $NSClientService.delete();
  2327. if ($result.ReturnValue -eq 0) {
  2328. $this.info('NSClient++ Service has been removed');
  2329. } else {
  2330. $this.error('Failed to remove NSClient++ service');
  2331. }
  2332. } else {
  2333. $this.info('NSClient++ Service is not installed')
  2334. }
  2335. }
  2336. }
  2337.  
  2338. #
  2339. # In case we want to do more with the NSClient, we can auto-generate
  2340. # all NSClient++ config attributes
  2341. #
  2342. $installer | Add-Member -membertype ScriptMethod -name 'addNSClientDefaultConfig' -value {
  2343. if ($this.config('nsclient_add_defaults')) {
  2344. [string]$NSClientBinary = $this.getNSClientDefaultExecutablePath();
  2345.  
  2346. if ($NSClientBinary -eq '') {
  2347. $this.error('Unable to generate NSClient++ default config. Executable nscp.exe could not be found ' +
  2348. 'on default locations or the specified custom location. If you installed the NSClient on a ' +
  2349. 'custom location, please specify the path with -NSClientDirectory');
  2350. return;
  2351. }
  2352.  
  2353. if (Test-Path ($NSClientBinary)) {
  2354. $this.info('Generating all default NSClient++ config values');
  2355. $result = $this.startProcess($NSClientBinary, $TRUE, 'settings --generate --add-defaults --load-all');
  2356. if ($result.Get_Item('exitcode') -ne 0) {
  2357. $this.error($result.Get_Item('message'));
  2358. }
  2359. } else {
  2360. $this.error('Failed to generate NSClient++ defaults config. Path to executable is not valid: ' + $NSClientBinary);
  2361. }
  2362. }
  2363. }
  2364.  
  2365. #
  2366. # Deprecated function
  2367. #
  2368. $installer | Add-Member -membertype ScriptMethod -name 'installIcinga2Agent' -value {
  2369. $this.warn('The function "installIcinga2Agent" is deprecated and will be removed soon. Please use "install" instead.')
  2370. return $this.install();
  2371. }
  2372. $installer | Add-Member -membertype ScriptMethod -name 'installMonitoringComponents' -value {
  2373. $this.warn('The function "installMonitoringComponents" is deprecated and will be removed soon. Please use "install" instead.')
  2374. return $this.install();
  2375. }
  2376.  
  2377. #
  2378. # This function will try to load all
  2379. # data from the system and setup the
  2380. # entire Agent without user interaction
  2381. # including download and update if
  2382. # specified. Returnd 0 or 1 as exit code
  2383. #
  2384. $installer | Add-Member -membertype ScriptMethod -name 'install' -value {
  2385. try {
  2386. if (-Not $this.isAdmin()) {
  2387. return 1;
  2388. }
  2389.  
  2390. # Write an output to the logfile only, ensuring we always get a proper 'start entry' for the user
  2391. $this.info('Started script run...');
  2392. # Get the current API-Version from the Icinga Director
  2393. $this.getIcingaDirectorVersion();
  2394. # Convert our DirectorHostObject argument from Object to String if required
  2395. $this.convertDirectorHostObjectArgument();
  2396. # Read arguments for auto config from the Icinga Director
  2397. # At first only with our public key for global config attributes
  2398. $this.fetchArgumentsFromIcingaDirector($TRUE);
  2399. # Read the Host-API Key in case it exists
  2400. $this.readHostAPIKeyFromDisk();
  2401. # Get host name or FQDN if required
  2402. $this.fetchHostnameOrFQDN();
  2403. # Get IP-Address of host
  2404. $this.fetchHostIPAddress();
  2405. # Transform the hostname if required
  2406. $this.doTransformHostname();
  2407. # Try to create a host object inside the Icinga Director
  2408. $this.createHostInsideIcingaDirector();
  2409. # Load the configuration again, but this time with our
  2410. # Host key to fetch additional informations like endpoints
  2411. $this.fetchArgumentsFromIcingaDirector($FALSE);
  2412. # First check if we should get some parameters from the Icinga Director
  2413. $this.fetchTicketFromIcingaDirector();
  2414.  
  2415. # Try to locate the current
  2416. # Installation data from the Agent
  2417. if ($this.isAgentInstalled()) {
  2418. if (-Not $this.isAgentUpToDate()) {
  2419. if ($this.allowAgentUpdates()) {
  2420. $this.printAgentUpdateMessage();
  2421. $this.updateAgent();
  2422. $this.cleanupAgentInstaller();
  2423. }
  2424. } else {
  2425. $this.info('Icinga 2 Agent is up-to-date. Nothing to do.');
  2426. }
  2427. } else {
  2428. if ($this.canInstallAgent()) {
  2429. $this.installAgent();
  2430. $this.cleanupAgentInstaller();
  2431. # In case we have an API key assigned, write it to disk
  2432. $this.writeHostAPIKeyToDisk();
  2433. } else {
  2434. $this.warn('Icinga 2 Agent is not installed and not allowed of beeing installed.');
  2435. }
  2436. }
  2437.  
  2438. if (-Not $this.hasCertificates() -Or $this.forceCertificateGeneration()) {
  2439. $this.generateCertificates();
  2440. } else {
  2441. $this.info('Icinga 2 certificates already exist. Nothing to do.');
  2442. }
  2443.  
  2444. if ($this.shouldFlushIcingaApiDirectory()) {
  2445. $this.flushIcingaApiDirectory();
  2446. }
  2447.  
  2448. $this.generateIcingaConfiguration();
  2449. $this.applyPossibleConfigChanges();
  2450. $this.switchIcingaDebugLog();
  2451. $this.installIcingaAgentFirewallRule();
  2452. $this.installNSClient();
  2453.  
  2454. if ($this.madeChanges()) {
  2455. $this.restartAgent();
  2456. } else {
  2457. $this.info('No changes detected.');
  2458. }
  2459.  
  2460. # We modify the service user at the very last to ensure
  2461. # the user we defined for logging in is valid
  2462. $this.modifyIcingaServiceUser();
  2463. return $this.getScriptExitCode();
  2464. } catch {
  2465. $this.printLastException();
  2466. [void]$this.getScriptExitCode();
  2467. return 1;
  2468. }
  2469. }
  2470.  
  2471. #
  2472. # Deprecated function
  2473. #
  2474. $installer | Add-Member -membertype ScriptMethod -name 'uninstallIcinga2Agent' -value {
  2475. $this.warn('The function "uninstallIcinga2Agent" is deprecated and will be removed soon. Please use "uninstall" instead.')
  2476. return $this.uninstall();
  2477. }
  2478. $installer | Add-Member -membertype ScriptMethod -name 'uninstallMonitoringComponents' -value {
  2479. $this.warn('The function "uninstallMonitoringComponents" is deprecated and will be removed soon. Please use "uninstall" instead.')
  2480. return $this.uninstall();
  2481. }
  2482.  
  2483. #
  2484. # Removes the Icinga 2 Agent from the system
  2485. #
  2486. $installer | Add-Member -membertype ScriptMethod -name 'uninstall' -value {
  2487. $this.info('Trying to locate Icinga 2 Agent...');
  2488.  
  2489. if ($this.isAgentInstalled()) {
  2490. $this.info('Removing Icinga 2 Agent from the system...');
  2491. $result = $this.startProcess('MsiExec.exe', $TRUE, $this.getProperty('uninstall_id') + ' /q');
  2492.  
  2493. if ($result.Get_Item('exitcode') -ne 0) {
  2494. $this.error($result.Get_Item('message'));
  2495. return [int]$result.Get_Item('exitcode');
  2496. }
  2497.  
  2498. $this.info('Icinga 2 Agent successfully removed.');
  2499. }
  2500.  
  2501. if ($this.config('full_uninstallation')) {
  2502. $this.info('Flushing Icinga 2 program data directory...');
  2503. if (Test-Path ((Join-Path -Path $Env:ProgramData -ChildPath 'icinga2'))) {
  2504. try {
  2505. [System.Object]$folder = New-Object -ComObject Scripting.FileSystemObject;
  2506. [void]$folder.DeleteFolder((Join-Path -Path $Env:ProgramData -ChildPath 'icinga2'));
  2507. $this.info('Remaining Icinga 2 configuration successfully removed.');
  2508. } catch {
  2509. $this.exception('Failed to delete Icinga 2 Program Data Directory: ' + $_.Exception.Message);
  2510. }
  2511. } else {
  2512. $this.warn('Icinga 2 Agent program directory not present.');
  2513. }
  2514. }
  2515.  
  2516. if ($this.config('remove_nsclient')) {
  2517. $this.info('Trying to remove installed NSClient++...');
  2518.  
  2519. $nsclient = Get-WmiObject -Class Win32_Product |
  2520. Where-Object {
  2521. $_.Name -match 'NSClient*';
  2522. }
  2523.  
  2524. if ($nsclient -ne $null) {
  2525. $this.info('Removing installed NSClient++...');
  2526. [void]$nsclient.Uninstall();
  2527. $this.info('NSClient++ has been successfully removed.');
  2528. } else {
  2529. $this.warn('NSClient++ could not be located on the system. Nothing to remove.');
  2530. }
  2531. }
  2532.  
  2533. return $this.getScriptExitCode();
  2534. }
  2535.  
  2536. # Make the installation / uninstallation of the script easier and shorter
  2537. [int]$installerExitCode = 0;
  2538. [int]$uninstallerExitCode = 0;
  2539. # If flag RunUninstaller is set, do the uninstallation of the components
  2540. if ($RunUninstaller) {
  2541. $uninstallerExitCode = $installer.uninstall();
  2542. }
  2543. # If flag RunInstaller is set, do the installation of the components
  2544. if ($RunInstaller) {
  2545. $installerExitCode = $installer.install();
  2546. }
  2547. if ($RunInstaller -Or $RunUninstaller) {
  2548. if ($installerExitCode -ne 0 -Or $uninstallerExitCode -ne 0) {
  2549. return 1;
  2550. }
  2551. }
  2552.  
  2553. # Otherwise handle everything as before
  2554. return $installer;
  2555. }
Add Comment
Please, Sign In to add comment