I’m keeping this original post active for historical reference, but you should not use the below, since I have an update: Step 3 is dead. Long live step 3.1
This is it. The endgame. Here I’ll give you the final steps in configuring my server. We started with creating the virtual machine then moved to configuring the disks. We’re at the end – where’s it’s time to do the final configurations.
In summary, we’re going to do several steps here. They pretty much follows my other guide step-by-step, so I’ll be brief in covering them here.
- Variable Declaration
- Installing Windows Features
- Enabling Disk Performance Metrics
- Installing some Utilities
- Copying the IIS Folders to a new Location
- Enable Deduplication (optional)
- Removing unnecessary IIS Websites and Application Pools
- Tweaking the IIS Settings
- Tweaking the ASP.NET Settings
- Creating a location for the TFTP and SFTP Roots (for NCM)
- Configuring Folder Redirection
- Pre-installing ODBC Drivers (for SAM Templates)
It seems like a lot, but each script snippet is small.
Variable Declaration
This is just where we setup the drives where we’ll keep the various parts of the install.
#region Variable Declaration $PageFileDrive = "D:\" $ProgramsDrive = "E:\" $WebDrive = "F:\" $LogDrive = "G:\" #endregion
Installing Windows Features
This is simple – we add the necessary Windows Features. The SolarWinds Orion installer can do this, but I like doing it in advance so that I can tweak some settings. Note – if you don’t want to use Data Deduplication for your log files, you can omit that feature and skip that script part where we enable the deduplication of the log drive.
#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
Enabling Disk Performance Metrics
Like I said before, I got used to seeing these in my Task Manager from my Windows Client machines, so I wanted to put them back.
#region Enable Disk Performance Counters in Task Manager Start-Process -FilePath "C:\Windows\System32\diskperf.exe" -ArgumentList "-Y" -Wait #endregion
Installing some Utilities
Here I install NotePad++, 7-zip, and a few other tools that I like.
#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", "\\Path\To\Installer\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 "\\Path\To\Installer\npp.latest.Installer.exe" -ArgumentList "/S" -Wait #endregion #region Setup UTILS Folder # This contains the SysInternals and Unix Utils that I love so much. $RemotePath = "\\Path\To\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 have access to the new path" [Environment]::SetEnvironmentVariable("Path", $MachinePathVariable, "Machine") } else { Write-Host "[$( $LocalPath )] already contained in machine environment variable 'Path'" } #endregion
Copying the IIS Folders to a new Location
I don’t like running my web-stuff on the operating system disk. So, I’m moving it elsewhere. Actually, I’m copying it because I want to use the original location as a source for the permissions that I’ll need
#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
Enable Deduplication
Super-simple here – just turn on Deduplication for the log drive.
#region Enable Deduplication on the Log Drive Enable-DedupVolume -Volume ( $LogDrive.Replace("\", "") ) Set-DedupVolume -Volume ( $LogDrive.Replace("\", "") ) -MinimumFileAgeDays 0 -OptimizeInUseFiles -OptimizePartialFiles #endregion
Removing unnecessary IIS Websites and Application Pools
Since Orion will install it’s own website and application pools, I can remove the existing ones to save on resources.
#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
Tweaking the IIS Settings
This is the most complex portion. The summary is that I’ll be opening up the IIS and ASP.NET configuration files and changing a bunch of settings. I do this using the System.Xml.XmlDocument type because it’s much easier to navigate. A summary of the changes made can be found on my original post.
#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
Tweaking the ASP.NET Configuration Settings
Also complex, but handled the same way.
#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
Creating a location for the TFTP and SFTP Roots
These two roots are going to be used after the installation, but I hate that they normally get put on the C:\ Drive. So I create the folders here and then I’ll have to change them in the application after we install it.
#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
Configuring Folder Redirection
This was (by far) the hardest part to script out – only because I wanted to add a few more changes from the original post. It turns out that “mklink” isn’t an executable. It’s a function (like “dir”) that it executed in the command shell. So figuring out the best way to handle this was a little tricky. I finally found the answer by simply executing a command shell and passing it the entire command.
What did I add? I moved the SolarWinds Log folder to the Log Drive and I moved the default program installation location to the Programs Drive. Now I don’t need to remember to change the target for the first installer!
You’ll notice the “Order” Property that I added. I did this because these need to be run in a specific order as to not cause linking failures (a link is being created to a subfolder, even through the parent folder doesn’t exist).
#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
Pre-installing ODBC Drivers
Because I use a bunch of different database engines in my environment that I want SAM to monitor, I have to install the ODBC drivers for each engine. The problem is that I always forget to install them until I’m actually trying to configure the Application Template. This annoys me, so I figured it might be better to install them in advance.
#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- share which includes the files that I want to install AFTER I install Orion. $Drivers = Get-ChildItem -Path "\\Path\To\ODBC\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
Results
So, if I put this all together and run it as an Administrator I get this:
The entire process took only 2:13! And of that the majority (1:44) was waiting for the Windows Features to install. This process (just step 3) used to take me at least 30 minutes with a checklist.
Now, I was able to go from no VM at all to Ready for SolarWinds Orion installation bits in under 20 minutes (10 of which are installing the O/S).
Last thing: Apply Windows updates and reboot. Then do it again. And again.
So that’s it. That’s my 3 step process to building better Orion Servers. Please feel free to provide feedback on anything you see here. I’m always happy to get it!
2 thoughts on “Building my Orion Server [Scripting Edition] – Step 3”