How To Install OMS Agent On Server Core

In this short post I’ll cover how to install OMS agent on a Server Core instance.

The first step is to download and extract the installer. This step must be performed on a machine with the Windows GUI installed, Server Core instances won’t work. To extract the installer run:

MMASetup-AMD64.exe /c [/t:ExtractPath]

The /t parameter is optional, you will be prompted to specify the extraction path in the GUI if it wasn’t specified.

To install OMS agent on a Server Core instance you have to run the installer silently by passing the /qn switch along with some other bits of information required by OMS.

See the example PowerShell script below:

$WorkSpaceID = "xxxxxx"
$WorkSpaceKey="xxxxx=="

$ArgumentList = ' /qn ADD_OPINSIGHTS_WORKSPACE=1 ' + "OPINSIGHTS_WORKSPACE_ID=$WorkspaceID " + "OPINSIGHTS_WORKSPACE_KEY=$WorkSpaceKey " + 'AcceptEndUserLicenseAgreement=1'

Start-Process '.\setup.exe'-ArgumentList $ArgumentList-ErrorAction Stop -Wait -Verbose |Out-Null
To confirm the OMS agent is installed you can run the following script:
Get-WmiObject -Query 'select * from win32_product where Name = "Microsoft Monitoring Agent"'
If it was successfully installed you will see the connected agent in the OMS portal after a while.
On a side note if you want to remove OMS Agent from a Server Core instance you can run the following script:
$agent = Get-WmiObject -Query 'select * from win32_product where Name = "Microsoft Monitoring Agent"'
$agent.Uninstall()

Francois Delport

Automating Installers .Net Config Files Scheduled Tasks And Services

This post is a continuation of the brain dump I started here.

Adding new settings to a .NET config file
Previously I showed how to manipulate the existing settings in a .NET/XML configuration file but adding new entries turned out to be a bit more typing  since you have to create the new xml element(s) and append them to the document.

For example to add the section in bold to the existing file:

<Configuration>
<CustomSettings>
<add key="ExistingSetting1Key" value="ExistingSetting1Value">
  <add key="Setting1Key" value="Setting1Value">
</CustomSettings
</Configuration>

You use the code below:

$file = 'C:\App\App.exe.config'
$xml = [xml](Get-Content $file)
$CustomSettings  = $xml.configuration.CustomSettings
$Setting1 = $xml.CreateElement("add")
$Setting1.SetAttribute("key", "SettingKey")
$Setting1.SetAttribute("value", "SettingValue")
$CustomSettings.AppendChild($Setting1 )
$xml.Save($file)

As you can see nothing really new if you are used to manipulating XML documents, just more typing.

Depending on the complexity of the xml and how much must be changed you could take a existing element, make a copy of it, manipulate it and then append it to the document as well.

Creating a scheduled task in using PowerShell
In case you did not know, Azure VMs recreate their NICs when you shut them down from the portal and I had a software component that was linked to a specific NIC. After starting up again it would loose the NIC you selected earlier. So as part of the automated deployment every time the machine boots up I have to update the configuration of my 3rd party component to select the correct NIC. One way to do this is to run a scheduled task at startup. Here is the PowerShell script to create a scheduled task, you need PowerShell 3.0 or higher and Windows 2012.

$action = New-ScheduledTaskAction -Execute 'Powershell.exe' -Argument '-File C:\Tools\SelectNIC.ps1 -NoProfile -WindowStyle Hidden'


$trigger = New-ScheduledTaskTrigger -AtStartup

Register-ScheduledTask -Action $action -Trigger $trigger -TaskName "SelectNIC" -Description "Change the NIC used to match licence"

Sadly my server was Windows 2008 so here is the command to do it using schtask.exe.

schtasks /Create /TR "Powershell.exe -File C:\Tools\SelectNIC.ps1 -NoProfile -WindowStyle Hidden" /SC ONSTART /RU account /RP password /TN NameForTheTask /RL HIGHEST

This specific task needed admin permissions so I had to use /RU and /RP and also /RL HIGHEST parameters to run with the highest privilege. You can also use schtask.exe to import scheduled tasks from xml files, this can be handy if it becomes too cumbersome to fit into the command line.

Changing the user for a service using PowerShell
Here is a simple PowerShell script to change the account used to run a service.

$filter = 'Name=' + "'" + $ServiceName + "'" + ''
$service = Get-WMIObject -ComputerName $CompName -namespace  "root\cimv2" -class Win32_Service -Filter $filter
$service.Change $null, $null, $null, $null, $null, $null, $newAcct, $newPass, $null, $null, $null

$service.StopService
sleep 10
$service.StartService

This one seems pretty forward except the parameters of the Change method on the $Service object differs depending on your version of Windows and may be the version of WMI as well. To see the correct parameters for your environment you can use the snippet below. It will display the methods on the $Service object in grid.

$Service | Get-Member -MemberType  Method | Out-GridView

One more thing to look out for is the format of the username, it must be in the domain\user format when using domain accounts or .\username format for the local account.

Changing the startup type of a service
Using PowerShell to change the startup type of a service is very easy:

Set-Service yourservicename -startuptype "manual"

But Powershell cannot change the startup type to automatic delayed start, you have to use the sc.exe command to do that.

sc.exe \\machinename config "ServiceName" start= delayed-auto

Note you need the space between start= and delayed-auto and the account running the service must have login as a service rights.

Francois Delport

Automating Installers

I’m still on my quest to automate the installation of a pretty complex environment and I’m writing down what I learnt in case I need it again one day or even better someone else will find it useful and save them some time.

Updating environment variables
There doesn’t seem to be a built in way to set environment variables in PowerShell but you can use the Environment class from .NET to retrieve the current value:

[Environment]::GetEnvironmentVariable("Path","Machine")

and to set the new value:

[Environment]::SetEnvironmentVariable("Path", "New Path Value.", "Machine")

I also saw another method editing the registry key that contains the Path variable but the problem is it doesn’t update it for the current PowerShell session, you have to restart PowerShell first. From my testing, using the Environment class immediately updates it for the current session and broadcasts the change so new sessions will pick it up without restarting.

The InstallShield interactive user problem
One of the 3rd party components I was installing via PowerShell worked perfectly well when you run the script from the command prompt but failed with permission denied errors when running the script from Octopus deploy or any other remote method. All the methods used the same account and it had admin rights on the box. It took a while and some googling to finally get to the bottom of it. It turned out to be the InstallShield Script Engine (ISScript) and in this environment specifically version 10.50. If you look at the DCOM configuration for that version you will see it is set to run as the interactive user which won’t work when you are running it from a non interactive session, it should actually be the launching user.

ISSCript

The other versions I encountered didn’t have this problem, they are all set to the launching user by default.

The installer needed this specific ISSsript version, if it is not present, the Setup.exe will install it. To get past this problem you have to explicitly install ISScript instead of leaving it up to the Setup.exe then change the DCOM config to use the launching user and then install the software using the MSI not the Setup.exe. If you use Setup.exe it will change your DCOM config back to the default before running the MSI installer. If you want to change the DCOM config from a script you can delete the RunAs registry key, make sure you have to correct AppID for the version of ISScript in this case {223D861E-5C27-4344-A263-0C046C2BE233} for 10.50.

DeleteRegKey

Config file encoding
I had a component failing with invalid configuration error messages even though the file looked fine and was even identical to the config file from a working system when doing a text compare. In retrospect I should have done a hex compare as well. Turns out the encoding of the file was UCS-2 LE BOM instead of UTF-8 and this happened because I was reading the file contents, making changes and then writing the file out from a collection of strings in PowersShell.

WrongEncoding

To fix it you have to specify the encoding when writing out the file in PowerShell.

Add-Content -Encoding UTF8 -Path C:\File.txt -Value ValuesToWrite

Capture PowerShell  output to file
When you are creating automated deployments it is not uncommon for scripts to work when you run them manually but fail during the deployment from a build server or other deployment automation tool. If you want to debug what is happening you can add your own Write-Output statements all over to trace where you are but this gets tedious very quickly. A better way I find is to use the PowerShell Transcript cmdlets.

Start-Transcript -path YourLogFile

and

Stop-Transcript

The filename is optional and if you omit it PowerShell does have some smarts built-in around overwriting and naming the file, you read more here.

Francois Delport

Automating Installers And Editing Config Files With PowerShell

In this post I’m going to take a deep dive into automating installers and editing config files with PowerShell.

I spent the last few weeks automating the installation and configuration of various 3rd party software components. It involved a fair bit of trail and error to get all of it working. I decided to compile all the scripts into a blog post in case someone else or even future me need a refresher or quick start.

Runnig MSI and Exe installers silently
MSI installers are easy to run silently and you can prevent it from rebooting the machine after installation. I also specified verbose logging, it comes in handy if the installer fails for some reason. Note the accent character  (`) used for escaping the double quotes (“) around the MSI file name. I enclosed the file name in double quotes in case it contained spaces. The second last line prints the command line parameters that are about to run to the console, in my case I was using Octopus and it was handy for debugging since the output is shown in the Octopus deployment log.

$msi = "/i `"AppToInstall.msi`""
$cmd = "MSIEXEC"
$q = "/qn"
$nrst = "/norestart"
$log = "/lv verbose.log"
$fullparam = $msi + " " + $q + " "+ $log + " " + $nrst
$fullparam  #optional, output the parameters to the console for debugging
Start-Process -file $cmd -arg $fullparam -PassThru | wait-process

You can write everything in a single line but I split the command and parameters up into different variables to make it more readable and easier when you want to change the script.

You also get Exe installers that wrap a MSI installer and in this case you have to pass some parameters to the Exe to make it silent /S and also a second set of parameters that it will pass to the MSI /V to hide the installer gui /qn and also to prevent rebooting in this case. Note the single qoutes (‘) on line two, this is another way to deal with double quotes in a string, PowerShell will treat this as a literal string.

$cmd = ".\Setup.exe"
$param = '/s /v"REBOOT=ReallySupress /qn"'  #note single qoutes
$cmd + " " + $param  #optional output to concole for debugging
Start-Process -file $cmd -arg $param -PassThru | wait-process

Note that everything after /V is passed as a single parameter to the MSI and should be enclosed in quotes.

Editing config files using PowerShell
The easiest ones are XML files, you find the node to change and you give it a value. In this case I am changing the InnerText of the element.

$file = 'PathToXmlFile.xml'
$xml = [xml](Get-Content $file)
$node = $xml.customconfig.usersettings.servers.FirstChild
$node.InnerText = 'localhost'
$xml.Save($file)

If you are dealing with .NET config files you can use the same technique as above. In this example I am changing the value of the UserEmailAddress setting in the AppSettings section. Note I am changing an attribute on the XML element this time.

$xml.configuration.appSettings.add | Foreach-Object {
if ( $_.key -eq 'UserEmailAddress' ) {
$_.value = "$internalEmailAddress";
}
}

You can also modify connection strings.

$xml.configuration.connectionStrings.add | Foreach-Object {
if ( $_.name -eq 'DefaultContext' ) {
$_.connectionString = "$connectionString";
$_.providerName = "$dbProvider";
}
}

Editing text files turned out to be a bit more work but luckily this was for a clean install so it always had the same values to start with. I build up a new config file by reading all the existing lines into a collection, replacing the current setting with the new value and then I overwrite the existing file with the new lines.

$fileName = "Custom.cfg"
$lines = Get-Content $fileName
$newlines = @()   #create the collection


Foreach($line in $lines) {
if ('[email protected]'.CompareTo($line) -eq 0)
{
$line =   $line.Replace('[email protected]',
'[email protected]')
}
$newlines += $line
}

Out-File $fileName -inputobject $newlines

Setting registry values was also pretty easy.

Set-ItemProperty -Path "HKLM:\Software\AppName\LicenseSettings" -Name "LicenseFile" -Value "PathToLicenceFile.lic"

I also had to create some environment variables, in this case it was a machine wide setting but you can also create one at user and process level.

[Environment]::SetEnvironmentVariable('USERCFG', 'PathToUserConfig.xml', 'Machine')

Hopefully this post covered most of the common tasks when it comes to automating installers, editing config files and registry settings to get you going quickly.

Francois Delport