SQL Server installer cache files are missing
http://go.microsoft.com/fwlink/?LinkId=199776
Target | Microsoft.KnowledgeServices.Windows.OperatingSystem | ||
Parent Monitor | System.Health.ConfigurationState | ||
Category | Alert | ||
Enabled | True | ||
Alert Generate | True | ||
Alert Severity | Warning | ||
Alert Priority | High | ||
Alert Auto Resolve | True | ||
Monitor Type | Microsoft.KnowledgeServices.Library.PowerShellMonitorEx | ||
Remotable | True | ||
Accessibility | Public | ||
Alert Message |
| ||
RunAs | Default | ||
Comment | SupportTopic=TBD;VersionNumber=1.0.0.0; |
<UnitMonitor ID="Monitor_AK80677" Comment="SupportTopic=TBD;VersionNumber=1.0.0.0;" Accessibility="Public" Enabled="true" Target="MicrosoftKnowledgeServicesWindowsLibrary!Microsoft.KnowledgeServices.Windows.OperatingSystem" ParentMonitorID="Health!System.Health.ConfigurationState" Remotable="true" Priority="High" TypeID="KnowledgeServicesLibrary!Microsoft.KnowledgeServices.Library.PowerShellMonitorEx" ConfirmDelivery="true">
<Category>Alert</Category>
<AlertSettings AlertMessage="MonitorMessagef76197d0136b4cf3a84ec3da066d8588">
<AlertOnState>Error</AlertOnState>
<AutoResolve>true</AutoResolve>
<AlertPriority>High</AlertPriority>
<AlertSeverity>Warning</AlertSeverity>
<AlertParameters>
<AlertParameter1>$Data/Context/Property[@Name='StrMissingInstallerCacheFilesXMLOutput']$</AlertParameter1>
</AlertParameters>
</AlertSettings>
<OperationalStates>
<OperationalState ID="Success" MonitorTypeStateID="Success" HealthState="Success"/>
<OperationalState ID="Error" MonitorTypeStateID="Error" HealthState="Error"/>
</OperationalStates>
<Configuration>
<ScriptName>AK80677.ps1</ScriptName>
<Parameters/>
<ScriptBody>
$ErrorActionPreference = "Stop"
# Set up the arguments
$scriptargs = new-object psobject
# Set up the output
$global:scriptoutput = new-object psobject
$scriptoutput | add-member NoteProperty "HasIssue" $false
$scriptoutput | add-member NoteProperty "StrMissingInstallerCacheFilesXMLOutput" ""
#-----------------------------------------------------
# MAIN CODE SECTION
#-----------------------------------------------------
# Environment
$scriptenv = New-Object System.Management.Automation.PSObject
$scriptenv | Add-Member NoteProperty "RuntimeError" $false
$scriptenv | Add-Member NoteProperty "ProductRegPaths" @()
$scriptenv | Add-Member NoteProperty "MissingInstallerCacheFiles" @()
$scriptenv | Add-Member NoteProperty "MissingInstallerCacheFilesXMLEnv" ""
$scriptServerComEnv = New-Object System.Management.Automation.PSObject
$scriptServerComEnv | Add-Member NoteProperty "HKEY_LOCAL_MACHINE" "HKLM"
$scriptServerComEnv | Add-Member NoteProperty "ServerComponents" @("SQL","RS","OLAP")
$scriptServerComEnv | Add-Member NoteProperty "SoftwareRegPathx86" "SOFTWARE\Microsoft\Microsoft SQL Server\"
$scriptServerComEnv | Add-Member NoteProperty "SoftwareRegPathx64" "SOFTWARE\Wow6432Node\Microsoft\Microsoft SQL Server\"
$scriptServerComEnv | Add-Member NoteProperty "ProductsRegPath" "SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\"
$scriptServerComEnv | Add-Member NoteProperty "PatchesRegPath" "SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Patches\"
#region Common private functions
Function IsExistRegKey($keyPath)
{
$isExistRegKeyValue = $false
if($keyPath)
{
if(Test-Path -Path ("{0}:\{1}" -f $scriptServerComEnv.HKEY_LOCAL_MACHINE,$keyPath))
{
$isExistRegKeyValue = $true
}
}
return $isExistRegKeyValue
}
Function IsExistRegKeyValue($keyPath,$valuName)
{
$isExistRegKeyValue = $false
if($keyPath -and $valueName)
{
if(IsExistRegKey $keyPath)
{
$key = "{0}:\{1}" -f $scriptServerComEnv.HKEY_LOCAL_MACHINE,$keyPath
if(Get-ItemProperty $key $valueName -ErrorAction SilentlyContinue)
{
$isExistRegKeyValue = $true
}
}
}
$isExistRegKeyValue
}
Function GetRegKeyNames($keyPath)
{
if(IsExistRegKey $keyPath)
{
Get-ChildItem -Path ("{0}:\{1}" -f $scriptServerComEnv.HKEY_LOCAL_MACHINE,$keyPath) | ForEach-Object{
$_.Name.Substring($_.Name.LastIndexOf("\") + 1,$_.Name.Length - $_.Name.LastIndexOf("\") - 1)
}
}
}
Function GetRegKeyValueNames($keyPath)
{
if(IsExistRegKey $keyPath)
{
(Get-Item -Path ("{0}:\{1}" -f $scriptServerComEnv.HKEY_LOCAL_MACHINE,$keyPath)).GetValueNames()
}
}
Function GetRegKeyValueData($keyPath,$valueName)
{
if(IsExistRegKeyValue $keyPath $valueName)
{
$key = "{0}:\{1}" -f $scriptServerComEnv.HKEY_LOCAL_MACHINE,$keyPath
(Get-ItemProperty $key $valueName).$valueName
}
}
Function ModifyString($strInput)
{
if($strInput)
{
$result = ""
for($i = 1; $i -le $strInput.Length/2; $i++)
{
$result += (ReverseString ($strInput.Substring($i*2-2,2)))
}
$result
}
}
Function ReverseString($string)
{
if($string)
{
$charArr = $string.ToCharArray()
[array]::Reverse($charArr)
[string]::Join("",$charArr)
}
}
Function ConvertCodeToGuid($productCode)
{
if($productCode)
{
$arrTokens = @()
$arrTokens += $productCode.Substring($productCode.IndexOf("{")+1,$productCode.LastIndexOf("}")-1).Split("-")
$subGuid = (ReverseString $arrTokens[0]) + (ReverseString $arrTokens[1]) + (ReverseString $arrTokens[2])
$subGuid += (ModifyString $arrTokens[3]) + (ModifyString $arrTokens[4])
$subGuid
}
}
Function IsSqlAssociatedProduct([string]$productName)
{
if(-not $productName)
{
return $null
}
$isSqlAssociatedProduct = $false
$prodList = @("sql server 2008","sql server 2012","vss writer","sql server compact 3.5","sql server system clr","report viewer redistributable 2008","report viewer 2012","sql server data tools","prerequisites for ssdt")
$prodTypeList = @("bpa","database engine services","analysis services","reporting services")
foreach($prod in $prodList)
{
if($productName -like "*{0}*" -f $prod)
{
$flag = 0
for($i = 0; $i -ne $prodTypeList.Length; $i++)
{
if($productName -notlike "*{0}*" -f $prodTypeList[$i])
{
$flag++
}
}
if($flag -eq $prodTypeList.Length)
{
$isSqlAssociatedProduct = $true
break
}
}
}
$isSqlAssociatedProduct
}
Function IsExistLocalPackage($packagePathInput)
{
$isExistLocalPackage = $false
if($packagePathInput)
{
$query = "select * from CIM_DataFile where name='{0}'" -f $packagePathInput.Replace("\","\\").Replace("'","\'")
$localPackageObj = Get-WmiObject -Query $query
if($localPackageObj -ne $null -and $localPackageObj.Count -ne 0)
{
$isExistLocalPackage = $true
}
}
$isExistLocalPackage
}
Function IsEqual(
[System.Management.Automation.PSObject]$param1, `
[System.Management.Automation.PSObject]$param2)
{
$isEaual = $false
if($param1 -and $param2)
{
if($param1.DisplayNameCI.ToString().Trim() -eq $param2.DisplayNameCI.ToString().Trim() -and `
$param1.LocalPackageCI.ToString().Trim() -eq $param2.LocalPackageCI.ToString().Trim())
{
$isEaual = $true
}
}
$isEaual
}
Function NewMissingFileRecord($displayName,$localPackage)
{
$missingFileInfo = New-Object System.Management.Automation.PSObject
$missingFileInfo | Add-Member NoteProperty "DisplayNameCI" ""
$missingFileInfo | Add-Member NoteProperty "LocalPackageCI" ""
$missingFileInfo.DisplayNameCI = $displayName
$missingFileInfo.LocalPackageCI = $localPackage
return $missingFileInfo
}
Function FormatXMLData([array]$arrayList)
{
$xmlTemplate = "<row><DisplayName>{0}</DisplayName><LocalPackage>{1}</LocalPackage></row>"
if($arrayList)
{
$arrayList | ForEach-Object{
$scriptenv.MissingInstallerCacheFilesXMLEnv += ($xmlTemplate -f $_.DisplayNameCI,$_.LocalPackageCI)
}
}
}
Function GetProductGuids($prodOrPatchRegPath)
{
$productGuidList = @()
if(IsExistRegKey $prodOrPatchRegPath)
{
foreach($serverComponent in $scriptServerComEnv.ServerComponents)
{
$componentPath = "{0}Instance Names\{1}" -f $prodOrPatchRegPath,$serverComponent
foreach($instanceId in (GetRegKeyValueNames $componentPath))
{
$instanceSetupRegPath = "{0}{1}\Setup\" -f $prodOrPatchRegPath,(GetRegKeyValueData $componentPath $instanceId)
$productCode = GetRegKeyValueData $instanceSetupRegPath "ProductCode"
if($productCode)
{
$productGuidList += ConvertCodeToGuid $productCode
}
}
}
}
$productGuidList
}
Function CheckInstallPropertiesKey($productRegPath)
{
if($productRegPath)
{
$localPackageRegPath = "{0}\InstallProperties\" -f $productRegPath
$localPackage = GetRegKeyValueData $localPackageRegPath "LocalPackage"
$displayName = GetRegKeyValueData $localPackageRegPath "DisplayName"
if($localPackage)
{
if(-not (IsExistLocalPackage $localPackage))
{
$scriptenv.MissingInstallerCacheFiles += NewMissingFileRecord $displayName $localPackage
}
}
}
}
Function CheckInstallPatchesKey($productRegPath)
{
if($productRegPath)
{
$patchesRegPath = "{0}\Patches\" -f $productRegPath
$patchGuids = @()
$processedPatchGuids = @()
$patchGuids += GetRegKeyNames $patchesRegPath
if($patchGuids)
{
foreach($patchGuid in $patchGuids)
{
if($processedPatchGuids -notcontains $patchGuid)
{
$processedPatchGuids += $patchGuid
$patchPathRegPath = ("{0}{1}" -f $scriptServerComEnv.PatchesRegPath,$patchGuid)
$localPackage = GetRegKeyValueData $patchPathRegPath "LocalPackage"
if($localPackage -and -not (IsExistLocalPackage $localPackage))
{
$patchRegPath = "{0}{1}" -f $patchesRegPath,$patchGuid
$displayName = GetRegKeyValueData $patchRegPath "DisplayName"
$scriptenv.MissingInstallerCacheFiles += NewMissingFileRecord $displayName $localPackage
}
}
}
}
}
}
#endregion Common private functions
#Step 1:
# Get the sql components registry key and add the keys to dictionary
# The registry keys to get in format of:
# SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\xxxxxxxx,
# where xxxxxxxx is the product GUID.
# To get the product GUID:
# a) Get the instance id from registry value "SQL_ROOT\Instance Names\[InstanceName]"
# b) Get the setup registry key: SQL_ROOT\[InstanceID]\Setup\
# c) Get ProductCode from registry value SQL_ROOT\[InstanceID]\Setup\ProductCode
# d) Calculate product GUID from [ProductCode]
# e) Add the product GUID to the dictionary.
Function GetProductGuidsWithPath()
{
$productGuids = @()
$productGuids += GetProductGuids $scriptServerComEnv.SoftwareRegPathx86
$productGuids += GetProductGuids $scriptServerComEnv.SoftwareRegPathx64
$productGuids | ForEach-Object{
if($_)
{
$prodGuidWithPath = "{0}{1}" -f $scriptServerComEnv.ProductsRegPath,$_
if($scriptenv.ProductRegPaths -notcontains $prodGuidWithPath)
{
$scriptenv.ProductRegPaths += $prodGuidWithPath
}
}
}
}
#Step 2:
# Query product registry key for components associated with SQL Server that
# are not part of the service.
# a) Enum all the sub keys under
# SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products
# b) For each sub key, get the registry value of PRODUCTS_ROOT\\InstallProperties\DisplayName
# c) Check [DisplayName] to see if it is associated to SQL. If yes, add the sub key to dictionary.
Function GetProductGuidsWithPathAndDisplayName()
{
GetRegKeyNames $scriptServerComEnv.ProductsRegPath | ForEach-Object{
$displayName = GetRegKeyValueData ("{0}{1}\InstallProperties\" -f $scriptServerComEnv.ProductsRegPath,$_) "DisplayName"
if(IsSqlAssociatedProduct $displayName)
{
if($scriptenv.ProductRegPaths -notcontains $_)
{
$scriptenv.ProductRegPaths += "{0}{1}" -f $scriptServerComEnv.ProductsRegPath,$_
}
}
}
}
#Step 3: Enum each registry key PRODUCTS_ROOT\[ProductGuid] in the dictionary, do
# a) Get value LocalPackage and DisplayName under PRODUCTS_ROOT\[ProductGuid]\InstallProperties\
# b) [LocalPackage] is a local file path. Check if the file exist.
# c) If not exist, add to the missed installer list
# d) Enum sub keys under PRODUCTS_ROOT\[ProductGuid]\Patches\, do
# d.1) Get LocalPackage and DisplayName value under PRODUCTS_ROOT\[ProductGuid]\Patches\[PatchGuid]\
# d.2) [LocalPackage] is a local file path. Check if the file exist.
# d.3) If not exist, add to the missed installer list
Function GetMissingFilesByProductGuid()
{
$scriptenv.ProductRegPaths | ForEach-Object{
CheckInstallPropertiesKey $_
CheckInstallPatchesKey $_
}
}
#Step 4: add the missed installer list to the property bag and return.
Function GenerateXMLReport()
{
if($scriptenv.MissingInstallerCacheFiles)
{
$tempMICFs = @()
#Remove duplicate records
foreach($micf in $scriptenv.MissingInstallerCacheFiles)
{
if(-not $tempMICFs)
{
$tempMICFs += $micf
}
if(-not ($tempMICFs | Where-Object{IsEqual $micf $_}))
{
$tempMICFs += $micf
}
}
FormatXMLData $tempMICFs
}
}
# Main function
function AdvisorRule($scriptargs, $scriptoutput)
{
# All parameters should be populated outside of the main function.
# The main function should only include the detection logic so that it can be easily reused by the Atlanta authoring tool.
trap [Exception] {
$scriptenv.RuntimeError = $true
continue;
}
# Initialize parameters
$scriptoutput.HasIssue = $false
$scriptoutput.StrMissingInstallerCacheFilesXMLOutput = ""
# Set parameter values
GetProductGuidsWithPath
GetProductGuidsWithPathAndDisplayName
GetMissingFilesByProductGuid
GenerateXMLReport
if($scriptenv.MissingInstallerCacheFiles)
{
if($scriptenv.RuntimeError -eq $false)
{
$scriptoutput.HasIssue = $true
$scriptoutput.StrMissingInstallerCacheFilesXMLOutput = $scriptenv.MissingInstallerCacheFilesXMLEnv
}
}
}
AdvisorRule $scriptargs $scriptoutput
# set the output
$mom = new-object -comobject "MOM.ScriptAPI"
$bag = $mom.CreatePropertyBag()
if ($scriptoutput.HasIssue -ne $null)
{
$bag.AddValue("HasIssue", $scriptoutput.HasIssue)
}
if ($scriptoutput.StrMissingInstallerCacheFilesXMLOutput -ne $null)
{
$bag.AddValue("StrMissingInstallerCacheFilesXMLOutput", $scriptoutput.StrMissingInstallerCacheFilesXMLOutput)
}
$bag
</ScriptBody>
<SnapIns/>
<TimeoutSeconds>300</TimeoutSeconds>
<Schedule>86393</Schedule>
<ErrorExpression>
<SimpleExpression>
<ValueExpression>
<XPathQuery Type="Boolean">Property[@Name='HasIssue']</XPathQuery>
</ValueExpression>
<Operator>Equal</Operator>
<ValueExpression>
<Value Type="Boolean">true</Value>
</ValueExpression>
</SimpleExpression>
</ErrorExpression>
<SuccessExpression>
<Not>
<Expression>
<SimpleExpression>
<ValueExpression>
<XPathQuery Type="Boolean">Property[@Name='HasIssue']</XPathQuery>
</ValueExpression>
<Operator>Equal</Operator>
<ValueExpression>
<Value Type="Boolean">true</Value>
</ValueExpression>
</SimpleExpression>
</Expression>
</Not>
</SuccessExpression>
</Configuration>
</UnitMonitor>