A few months ago, I wrote about the third step in setting up an Orion server from scratch. After using this script for a few months, I realized there was an eventuality that I missed.
What did I miss? It’s actually something simple that I overlooked. When you run the Configuration Wizard a second time (which you do after updates), it “saves” the old SolarWinds web folder for a fallback.
This causes a problem (for me at least). Since I wanted to change all the web site files to a different drive, this legacy folder (SolarWinds.backup.*) is the only one on the second drive and the new “SolarWinds” folder is still on the same drive as the inetpub folder.
To do this, we need to create the redirection for the inetpub folder before we install the Windows Features. This is done by re-purposing the redirection portion of the script that I use later, and just moving it earlier.
This is the part in question.
#region Redirect IIS.
$Redirections = @()
$Redirections += New-Object -TypeName PSObject -Property ( [ordered]@{ Order = [int]6; SourcePath = "C:\inetpub"; TargetDrive = $WebDrive } )
$Redirections | Add-Member -MemberType ScriptProperty -Name TargetPath -Value { $this.SourcePath.Replace("C:\", $this.TargetDrive ) } -Force
ForEach ( $Redirection in $Redirections | Sort-Object -Property Order )
{
# Check to see if the target path exists - if not, create the target path
if ( -not ( Test-Path -Path $Redirection.TargetPath -ErrorAction SilentlyContinue ) )
{
Write-Host "Creating Path for Redirection [$( $Redirection.TargetPath )]" -ForegroundColor Yellow
New-Item -ItemType Directory -Path $Redirection.TargetPath | Out-Null
}
# Build the string to send to the command prompt
$CommandString = "mklink /D /J `"$( $Redirection.SourcePath )`" `"$( $Redirection.TargetPath )`""
Write-Host "Executing [$CommandString]... " -ForegroundColor Yellow -NoNewline
# Execute it
Start-Process -FilePath "cmd.exe" -ArgumentList "/C", $CommandString -Wait
Write-Host "[COMPLETED]" -ForegroundColor Green
}
#endregion
Because of that, we don’t need to copy and apply the permissions to the inetpub
folder that was moved.
#region Copy the IIS Root to the Web Drive
# I can do this with Copy-Item, but I find that robocopy works better at keeping permissions
Start-Process -FilePath "robocopy.exe" -ArgumentList "C:\inetpub", ( Join-Path -Path $WebDrive -ChildPath "inetpub" ), "/E", "/R:3", "/W:5" -Wait
#endregion
#region Fix IIS temp permissions
$FolderPath = Join-Path -Path $WebDrive -ChildPath "inetpub\temp"
$CurrentACL = Get-Acl -Path $FolderPath
$AccessRule = New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule -ArgumentList "NT AUTHORITY\NETWORK SERVICE", "FullControl", ( "ContainerInherit", "ObjectInherit" ), "None", "Allow"
$CurrentACL.SetAccessRule($AccessRule)
$CurrentACL | Set-Acl -Path $FolderPath
#endregion
So that makes the complete script a little smaller, but no less complex. If you want step-by-step details, see my previous Step 3 posts.
# Configure-OrionServer.ps1
#region Variable Declaration
$ProgramsDrive = "E:\"
$WebDrive = "F:\"
$LogDrive = "G:\"
#endregion
#region Redirect IIS.
$Redirections = @()
$Redirections += New-Object -TypeName PSObject -Property ( [ordered]@{ Order = [int]6; SourcePath = "C:\inetpub"; TargetDrive = $WebDrive } )
$Redirections | Add-Member -MemberType ScriptProperty -Name TargetPath -Value { $this.SourcePath.Replace("C:\", $this.TargetDrive ) } -Force
ForEach ( $Redirection in $Redirections | Sort-Object -Property Order )
{
# Check to see if the target path exists - if not, create the target path
if ( -not ( Test-Path -Path $Redirection.TargetPath -ErrorAction SilentlyContinue ) )
{
Write-Host "Creating Path for Redirection [$( $Redirection.TargetPath )]" -ForegroundColor Yellow
New-Item -ItemType Directory -Path $Redirection.TargetPath | Out-Null
}
# Build the string to send to the command prompt
$CommandString = "mklink /D /J `"$( $Redirection.SourcePath )`" `"$( $Redirection.TargetPath )`""
Write-Host "Executing [$CommandString]... " -ForegroundColor Yellow -NoNewline
# Execute it
Start-Process -FilePath "cmd.exe" -ArgumentList "/C", $CommandString -Wait
Write-Host "[COMPLETED]" -ForegroundColor Green
}
#endregion
#region Add Necessary Windows Features
# this is a list of the Windows Features that we'll need
# it's being filtered for those which are not already installed
$Features = Get-WindowsFeature -Name FileAndStorage-Services, File-Services, FS-FileServer, Storage-Services, Web-Server, Web-WebServer, Web-Common-Http, Web-Default-Doc, Web-Dir-Browsing, Web-Http-Errors, Web-Static-Content, Web-Health, Web-Http-Logging, Web-Log-Libraries, Web-Request-Monitor, Web-Performance, Web-Stat-Compression, Web-Dyn-Compression, Web-Security, Web-Filtering, Web-Windows-Auth, Web-App-Dev, Web-Net-Ext, Web-Net-Ext45, Web-Asp-Net, Web-Asp-Net45, Web-ISAPI-Ext, Web-ISAPI-Filter, Web-Mgmt-Tools, Web-Mgmt-Console, Web-Mgmt-Compat, Web-Metabase, NET-Framework-Features, NET-Framework-Core, NET-Framework-45-Features, NET-Framework-45-Core, NET-Framework-45-ASPNET, NET-WCF-Services45, NET-WCF-HTTP-Activation45, NET-WCF-MSMQ-Activation45, NET-WCF-Pipe-Activation45, NET-WCF-TCP-Activation45, NET-WCF-TCP-PortSharing45, MSMQ, MSMQ-Services, MSMQ-Server, FS-SMB1, User-Interfaces-Infra, Server-Gui-Mgmt-Infra, Server-Gui-Shell, PowerShellRoot, PowerShell, PowerShell-V2, PowerShell-ISE, WAS, WAS-Process-Model, WAS-Config-APIs, WoW64-Support, FS-Data-Deduplication | Where-Object { -not $_.Installed }
$Features | Add-WindowsFeature
#endregion
#region Enable Disk Performance Counters in Task Manager
Start-Process -FilePath "C:\Windows\System32\diskperf.exe" -ArgumentList "-Y" -Wait
#endregion
#region Install 7Zip
# This can now be skipped because I'm deploying this via Group Policy
# Start-Process -FilePath "C:\Windows\System32\msiexec.exe" -ArgumentList "/i", "\\demo.lab\files\Applications\7z1604-x64.msi", "/passive" -Wait
#endregion
#region Install Notepad++
# Install NotePad++ (current version)
# Still need to install the Plugins manually at this point, but this is a start
Start-Process -FilePath "\\demo.lab\files\Applications\Notepad++\npp.latest.Installer.exe" -ArgumentList "/S" -Wait
#endregion
#region Setup UTILS Folder
$RemotePath = "\\demo.lab\files\Applications\UTILS\"
$LocalPath = "C:\UTILS\"
Start-Process -FilePath "C:\Windows\System32\robocopy.exe" -ArgumentList $RemotePath, $LocalPath, "/E", "/R:3", "/W:5", "/MT:16" -Wait
$MachinePathVariable = [Environment]::GetEnvironmentVariable("Path", "Machine")
if ( -not ( $MachinePathVariable -like '*$( $LocalPath )*' ) )
{
$MachinePathVariable += ";$LocalPath;"
$MachinePathVariable = $MachinePathVariable.Replace(";;", ";")
Write-Host "Adding C:\UTILS to the Machine Path Variable" -ForegroundColor Yellow
Write-Host "You must close and reopen any command prompt windows to include to the new path"
[Environment]::SetEnvironmentVariable("Path", $MachinePathVariable, "Machine")
}
else
{
Write-Host "[$( $LocalPath )] already contained in machine environment variable 'Path'"
}
#endregion
#region Enable Deduplication on the Log Drive
if ( Get-WindowsFeature -Name FS-Data-Deduplication | Where-Object { $_.Installed } )
{
Enable-DedupVolume -Volume ( $LogDrive.Replace("\", "") )
Get-DedupVolume -Volume ( $LogDrive.Replace("\", "") ) | Set-DedupVolume -OptimizeInUseFiles -OptimizePartialFiles -MinimumFileAgeDays 0 -Verify $true
}
#endregion
#region Delete Unnecessary Web Stuff
Get-WebSite -Name "Default Web Site" | Remove-WebSite -Confirm:$false
Remove-WebAppPool -Name ".NET v2.0" -Confirm:$false
Remove-WebAppPool -Name ".NET v2.0 Classic" -Confirm:$false
Remove-WebAppPool -Name ".NET v4.5" -Confirm:$false
Remove-WebAppPool -Name ".NET v4.5 Classic" -Confirm:$false
Remove-WebAppPool -Name "Classic .NET AppPool" -Confirm:$false
Remove-WebAppPool -Name "DefaultAppPool" -Confirm:$false
#endregion
#region Change IIS Application Host Settings
# XML Object that will be used for processing
$ConfigFile = New-Object -TypeName System.Xml.XmlDocument
# Change the Application Host settings
$ConfigFilePath = "C:\Windows\System32\inetsrv\config\applicationHost.config"
# Load the Configuration File
$ConfigFile.Load($ConfigFilePath)
# Save a backup if one doesn't already exist
if ( -not ( Test-Path -Path "$ConfigFilePath.orig" -ErrorAction SilentlyContinue ) )
{
Write-Host "Making Backup of $ConfigFilePath with '.orig' extension added" -ForegroundColor Yellow
$ConfigFile.Save("$ConfigFilePath.orig")
}
# change the settings (create if missing, update if existing)
$ConfigFile.configuration.'system.applicationHost'.log.centralBinaryLogFile.SetAttribute("directory", [string]( Join-Path -Path $LogDrive -ChildPath "inetpub\logs\LogFiles" ) )
$ConfigFile.configuration.'system.applicationHost'.log.centralW3CLogFile.SetAttribute("directory", [string]( Join-Path -Path $LogDrive -ChildPath "inetpub\logs\LogFiles" ) )
$ConfigFile.configuration.'system.applicationHost'.sites.siteDefaults.logfile.SetAttribute("directory", [string]( Join-Path -Path $LogDrive -ChildPath "inetpub\logs\LogFiles" ) )
$ConfigFile.configuration.'system.applicationHost'.sites.siteDefaults.logfile.SetAttribute("logFormat", "W3C" )
$ConfigFile.configuration.'system.applicationHost'.sites.siteDefaults.logfile.SetAttribute("logExtFileFlags", "Date, Time, ClientIP, UserName, SiteName, ComputerName, ServerIP, Method, UriStem, UriQuery, HttpStatus, Win32Status, BytesSent, BytesRecv, TimeTaken, ServerPort, UserAgent, Cookie, Referer, ProtocolVersion, Host, HttpSubStatus" )
$ConfigFile.configuration.'system.applicationHost'.sites.siteDefaults.logfile.SetAttribute("period", "Hourly")
$ConfigFile.configuration.'system.applicationHost'.sites.siteDefaults.traceFailedRequestsLogging.SetAttribute("directory", [string]( Join-Path -Path $LogDrive -ChildPath "inetpub\logs\FailedReqLogFiles" ) )
$ConfigFile.configuration.'system.webServer'.httpCompression.SetAttribute("directory", [string]( Join-Path -Path $WebDrive -ChildPath "inetpub\temp\IIS Temporary Compressed File" ) )
$ConfigFile.configuration.'system.webServer'.httpCompression.SetAttribute("maxDiskSpaceUsage", "2048" )
$ConfigFile.configuration.'system.webServer'.httpCompression.SetAttribute("minFileSizeForComp", "5120" )
# Save the file
$ConfigFile.Save($ConfigFilePath)
Remove-Variable -Name ConfigFile -ErrorAction SilentlyContinue
#endregion
#region Change the ASP.NET Compilation Settings
# XML Object that will be used for processing
$ConfigFile = New-Object -TypeName System.Xml.XmlDocument
# Change the Compilation settings in teh ASP.NET Web Config
$ConfigFilePath = "C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\web.config"
Write-Host "Editing [$ConfigFilePath]" -ForegroundColor Yellow
# Load the Configuration File
$ConfigFile.Load($ConfigFilePath)
# Save a backup if one doesn't already exist
if ( -not ( Test-Path -Path "$ConfigFilePath.orig" -ErrorAction SilentlyContinue ) )
{
Write-Host "Making Backup of $ConfigFilePath with '.orig' extension added" -ForegroundColor Yellow
$ConfigFile.Save("$ConfigFilePath.orig")
}
# change the settings (create if missing, update if existing)
$ConfigFile.configuration.'system.web'.compilation.SetAttribute("tempDirectory", [string]( Join-Path -Path $WebDrive -ChildPath "inetpub\temp") )
$ConfigFile.configuration.'system.web'.compilation.SetAttribute("maxConcurrentCompilations", "16")
$ConfigFile.configuration.'system.web'.compilation.SetAttribute("optimizeCompilations", "true")
Write-Host "Saving [$ConfigFilePath]" -ForegroundColor Yellow
$ConfigFile.Save($ConfigFilePath)
# Save the file
$ConfigFile.Save($ConfigFilePath)
Remove-Variable -Name ConfigFile -ErrorAction SilentlyContinue
#endregion
#region Create SFTP and TFTP Roots on the Web Drive
# Check for & Configure SFTP and TFTP Roots
$Roots = "SFTP_Root", "TFTP_Root"
ForEach ( $Root in $Roots )
{
if ( -not ( Test-Path -Path ( Join-Path -Path $WebDrive -ChildPath $Root ) ) )
{
New-Item -Path ( Join-Path -Path $WebDrive -ChildPath $Root ) -ItemType Directory
}
}
#endregion
#region Folder Redirection
$Redirections = @()
$Redirections += New-Object -TypeName PSObject -Property ( [ordered]@{ Order = [int]1; SourcePath = "C:\ProgramData\SolarWinds"; TargetDrive = $ProgramsDrive } )
$Redirections += New-Object -TypeName PSObject -Property ( [ordered]@{ Order = [int]2; SourcePath = "C:\ProgramData\SolarWindsAgentInstall"; TargetDrive = $ProgramsDrive } )
$Redirections += New-Object -TypeName PSObject -Property ( [ordered]@{ Order = [int]3; SourcePath = "C:\Program Files (x86)\SolarWinds"; TargetDrive = $ProgramsDrive } )
$Redirections += New-Object -TypeName PSObject -Property ( [ordered]@{ Order = [int]4; SourcePath = "C:\Program Files (x86)\Common Files\SolarWinds"; TargetDrive = $ProgramsDrive } )
$Redirections += New-Object -TypeName PSObject -Property ( [ordered]@{ Order = [int]5; SourcePath = "C:\ProgramData\SolarWinds\Logs"; TargetDrive = $LogDrive } )
$Redirections | Add-Member -MemberType ScriptProperty -Name TargetPath -Value { $this.SourcePath.Replace("C:\", $this.TargetDrive ) } -Force
ForEach ( $Redirection in $Redirections | Sort-Object -Property Order )
{
# Check to see if the target path exists - if not, create the target path
if ( -not ( Test-Path -Path $Redirection.TargetPath -ErrorAction SilentlyContinue ) )
{
Write-Host "Creating Path for Redirection [$( $Redirection.TargetPath )]" -ForegroundColor Yellow
New-Item -ItemType Directory -Path $Redirection.TargetPath | Out-Null
}
# Build the string to send to the command prompt
$CommandString = "mklink /D /J `"$( $Redirection.SourcePath )`" `"$( $Redirection.TargetPath )`""
Write-Host "Executing [$CommandString]... " -ForegroundColor Yellow -NoNewline
# Execute it
Start-Process -FilePath "cmd.exe" -ArgumentList "/C", $CommandString -Wait
Write-Host "[COMPLETED]" -ForegroundColor Green
}
#endregion
#region Pre-Orion Install ODBC Drivers
#
# This is for any ODBC Drivers that I want to install to use with SAM
# You don't need to include any driver for Microsoft SQL Server - it will be done by the installer
# I have the drivers for MySQL and PostgreSQL in this share
#
# There is also a Post-Orion share which includes the files that I want to install AFTER I install Orion
$Drivers = Get-ChildItem -Path "\\demo.lab\files\Applications\SolarWinds\Orion\Drivers\Pre\" -File
ForEach ( $Driver in $Drivers )
{
if ( $Driver.Extension -eq ".exe" )
{
Write-Host "Executing $( $Driver.FullName )... " -ForegroundColor Yellow -NoNewline
Start-Process -FilePath $Driver.FullName -Wait
Write-Host "[COMPLETED]" -ForegroundColor Green
}
elseif ( $Driver.Extension -eq ".msi" )
{
# Install it using msiexec.exe
Write-Host "Installing $( $Driver.FullName )... " -ForegroundColor Yellow -NoNewline
Start-Process -FilePath "C:\Windows\System32\msiexec.exe" -ArgumentList "/i", "`"$( $Driver.FullName )`"", "/passive" -Wait
Write-Host "[COMPLETED]" -ForegroundColor Green
}
else
{
Write-Host "Bork-Bork-Bork on $( $Driver.FullName )"
}
}
#endregion
#region Import Certificate
# Lastly, import my internal PKI Certificate for use with HTTPS
$CertName = "WildcardCert_demo.lab"
$CertPath = "\\Demo.Lab\Files\Data\Certificates\"
$PfxFile = Get-ChildItem -Path $CertPath -Filter "$CertName.pfx"
$PfxPass = ConvertTo-SecureString -String ( Get-ChildItem -Path $CertPath -Filter "$CertName.password.txt" | Get-Content -Raw ) -AsPlainText -Force
Import-PfxCertificate -FilePath $PfxFile.FullName -Password $PfxPass -CertStoreLocation "Cert:\LocalMachine\My"
#endregion
Wow, these scripts are pure gold. Thank you for sharing – I’ve been depending on an automation tool to do most of the coding behind the scenes to accomplish automation – now I’m no longer able to use that tool so I’m currently in the process of learning powershell / powerCLI / node.js. Solarwinds is what I work with daily and I’m very familiar with the API – but as far as automated provisioning as you have outlined in the several articles you’ve posted here – I’m highly inexperienced (I would likely say the same with regards to my Powershell abilities). This material is going to be a huge part of me diving into Powershell with the end goal being able to understand and tweak what you have here. Being able to materialize a Solarwinds instance practically out of thin air is a worth goal to aspire to… I’m thinking that automating the provisioning of a SQL Server 2017 docker container would be a perfect add on to this. Thank you for the awesome work you have shared!