Tuesday, February 26, 2019

Get-SCSMObjectHistory commandlet obsolete in SCSM 2016 - an alternative

I found out my script to figure out the "Created By" value from a Service Request in SCSM was no longer functional.


it used to be :


$req = get-scsmclassinstance -class $cl -Filter "Id -eq $id"
$inf = Get-SCSMObjectHistory -Object $req
$inf.History[0].UserName
The command SCMSObjectHistory does no longer exist in SCSM


here is how to achieve the same again:
$cl = get-SCSMClass -displayName "Service Request"
$req = get-scsmclassinstance -class $cl -Filter "Id -eq 'SR2577'"
$Relations = Get-SCSMRelationshipInstance -SourceInstance $req -TargetInstance $req
foreach ($Relation in $Relations)
{
 if ((Get-SCSMRelationship ($Relation.RelationshipId)).DisplayName -eq "Created By User")
 {
  $ReqByName = ($relation.TargetObject).DisplayName
  break  #no need to continue the loop since there is only one Created By relation !
 }
}
so $req  contains the details of the service request again
and then you find all the relations for that request.
now step through all relations (all I get is a list of GUID's) but you can show the RelationsshipID, and if you feed that into the get-SCSMRelationship, you can see what relationship it is.
in my case I am only interested in the Created By User, so I step thru it till i find that one, and then I extract the display name of the user who created the service request.


you can do $Relations.TargetObject  and it will show a list of all related target objects - it may help you to find what you're looking for :)
I had to figure this out by trial on error as I could not find anything about it.


actually,


 

Tuesday, October 10, 2017

Reinstall SCOM 2016 agent without APM as task in SCOM

in some occasions you don't want to have the APM module installed at all because it can break IIS - if one of the Application Pools is using .NET 2.0 (Technet article)


pushing the agent via SCOM will always install also the APM module.
so I created a script which will uninstall the SCOM agent, and then reinstall it without APM.
reason for the uninstall is, I tried to do a repair as they suggest, however, the repair fails - complaining it is missing some data - I presume this is related to the fact that after installing the agent UR1 was applied, so the MOMAgent.msi that I was using is the base version without UR1.


obviously if you use the SCOM agent to reinstall itself, it has to launch a script which is independent of the SCOM agent, so the process uses a VBscript which creates a commandfile and then registers a runonce scheduled task to run 2 minutes later....


I have registered the task against the Agent class



The script task requires 2 parameters :
PrimaryManagementGroupName \\ManagementServername\Agentmanagement\amd64
(I have shared out the AgentManagement folder to everyone read-only)





first find in the registry where the uninstall string is located, and /I or /X are replaced with /qn /X
then scan the registry for the installed management groups, and store them into an array


MGNames(x), MGservers(x), MGports(x)


so the script collects that information, and then creates a batch file (first as txt file).
you have to create it as txt because security does not permit to create a .cmd directly....


the batchfile contains the following steps (each separated by a ping command as "sleep")
  1. copy Agent install files
  2. uninstall agent (UninstallString)
  3. Install agent with PrimaryManagementgroup
    the management server is pulled from the registry prior to uninstall
  4. install URx update
  5. if detected, a VBscript is executed to add it to the extra management groups
  6. delete the folder (rd /s /q folder) and it's contents


besides the batchfile, if the agent was registered to more than 1 management group, for every extra management group, it creates a VBscript to register the extra management group


so this is what the batchfile looks like that is created by the task


xcopy \\MGserver\Agentmanagement\amd64\* C:\windows\temp\amd64 /Y /R
ping 1.1.1.1 -n 10 /w 1000 > nul
MsiExec.exe /qn /X {742D699D-56EB-49CC-A04A-317DE01F31CD}
ping 1.1.1.1 -n 70 /w 1000 > nul
msiexec.exe /qn /i c:\windows\temp\amd64\Momagent.msi USE_SETTINGS_FROM_AD=0 ACTIONS_USE_COMPUTER_ACCOUNT=1 USE_MANUALLY_SPECIFIED_SETTINGS=1 MANAGEMENT_GROUP=MGgroup MANAGEMENT_SERVER_DNS=MGServer NOAPM=1 SECURE_PORT=MGport AcceptEndUserLicenseAgreement=1
ping 1.1.1.1 -n 70 /w 1000 > nul
msiexec.exe /p c:\windows\temp\amd64\KB3190029-amd64-Agent.msp
ping 1.1.1.1 -n 70 /w 1000 > nul
cscript.exe C:\windows\temp\amd64\AddMGgroup0.vbs
ping 1.1.1.1 -n 20 /w 1000 > nul
rd /s /q C:\windows\temp\amd64




and this is what the VBscript looks like that is created by the task:


Set objMSConfig = CreateObject("AgentConfigManager.MgmtSvcCfg")
Call objMSConfig.AddManagementGroup(MGname,MGServer,MGport)
If Err.number <> 0 Then
 wscript.echo ("Failed to add SCOM")
Else
 Set oShell = WScript.CreateObject("WScript.Shell")
 Set oAPI = CreateObject("MOM.ScriptAPI")
 set oShellEnv = oShell.Environment("Process")
 computerName = oShellEnv("ComputerName")
 strCommand = "cmd /c net stop HealthService & cmd /c net start HealthService"
 Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")
 Set objProcess = objWMIService.Get("Win32_Process")
 Set objProgram = objProcess.Methods_("Create").InParameters.SpawnInstanceobjProgram.CommandLine = strCommand
 Set strShell = objWMIService.ExecMethod("Win32_Process", "Create", objProgram)
 wscript.echo ("Management Group SCOM has been added")
 WScript.echo "Restarting SCOM Health Service on " & computerName
End If








so the above 2 files are created by the following task in SCOM (run at the agent)


on error resume next
Const HKLM = &H80000002 'HKEY_LOCAL_MACHINE
Dim oParams, strComputer, strKey, sourcefolder, Destinationfolder, VBfilename, CMDFile, delay
Dim FSO, objReg, strSubkey, arrSubkeys, wshShell,MGServer, MGServer1
Dim DisplayName, UninstallString, InstallString
Set oParams = WScript.Arguments
PrimaryMG = trim(oParams(0)) 'Management Group that should be registered again
AgentSourcePath = trim(oParams(1)) 'path to share and folder where Agent Install files can be found
Set oShell = wscript.CreateObject ("wscript.Shell")
set fs = CreateObject("Scripting.FileSystemObject")
strWindows = oShell.ExpandEnvironmentStrings("%windir%")
OutFolder = strWindows & "\temp\amd64"
Outputfile = strWindows & "\temp\amd64\OpsNoAPM.txt"
oShell.LogEvent 4, "OpsNoAPM.cmd: Going to create " & Outputfile
strComputer = "."
strKey = "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\"
'Get WMI object
Set objReg = GetObject("winmgmts://" & strComputer & "/root/default:StdRegProv")
objReg.EnumKey HKLM, strKey, arrSubkeys
'Loop registry key to find uninstall string for SCOM Agent
For Each strSubkey In arrSubkeys
 objReg.GetStringValue HKLM, strKey & strSubkey, "DisplayName" , DisplayName
 If Instr(DisplayName,"Microsoft Monitoring Agent") > 0 Then
  objReg.GetStringValue HKLM, strKey & strSubkey, "UninstallString", UninstallString
  if (instr(UninstallString,"/I") > 0 ) then
   UninstallString = replace(UninstallString,"/I","/qn /X ")
  Elseif (instr(UninstallString,"/X") > 0 ) then
   UninstallString = replace(UninstallString,"/X","/qn /X ")
  Else
   wscript.echo "no uninstall string found"
   wscript.quit
  end if
 End If
Next
'loop registry to retrieve all management groups
strKey = "SOFTWARE\Microsoft\Microsoft Operations Manager\3.0\Agent Management Groups\"
Set objReg = GetObject("winmgmts://./root/default:StdRegProv")
objReg.EnumKey HKLM, strKey, arrSubkeys
Dim arrMGGroups()
Dim arrMGservers()
Dim arrMGPorts()
size = Ubound(arrSubkeys)
Redim arrMGGroups(size)
Redim arrMGservers(size)
Redim arrMGPorts(size)
cnt = 0
For Each strSubkey In arrSubkeys
 arrMGGroups(cnt) = strSubkey
 objReg.GetStringValue HKLM, strKey & strSubkey & "\Parent Health Services\0", "NetworkName", MGServer
 objReg.GetDWORDValue HKLM, strKey & strSubkey & "\Parent Health Services\0", "Port", MGPort
 arrMGservers(cnt) = MGServer
 arrMGPorts(cnt) = MGPort
 cnt = cnt + 1
next

if NOT isnull(UninstallString) then
 if fs.FolderExists(OutFolder) then
  if fs.FileExists(Outputfile) then fs.DeleteFile(OutputFile)
  if fs.FileExists(ResultFile) then fs.DeleteFile(ResultFile)
 Else
  fs.CreateFolder(OutFolder)
 End if
'Create batchfile to uninstall+install+patch+register extra MG group(s)
 Set out = fs.CreateTextFile(Outputfile)
 out.writeline "xcopy " & AgentSourcePath & "\* " & outFolder & " /Y /R"
 out.writeline "ping 1.1.1.1 -n 10 /w 1000 " & chr(62) & " nul"
 out.writeline UninstallString
 out.writeline "ping 1.1.1.1 -n 70 /w 1000 " & chr(62) & " nul"
 'register back with the same management server
 for x = 0 to size
  if arrMGGroups(x) = PrimaryMG then
   MGServer1 =  arrMGservers(x)
   MGPort = arrMGPorts(x)
  end if
 next
 out.writeline "msiexec.exe /qn /i c:\windows\temp\amd64\Momagent.msi USE_SETTINGS_FROM_AD=0 ACTIONS_USE_COMPUTER_ACCOUNT=1 USE_MANUALLY_SPECIFIED_SETTINGS=1 MANAGEMENT_GROUP=" & PrimaryMG & " MANAGEMENT_SERVER_DNS=" & MGServer1 & " NOAPM=1 SECURE_PORT=" & MGPort & " AcceptEndUserLicenseAgreement=1"
 out.writeline "ping 1.1.1.1 -n 70 /w 1000 " & chr(62) & " nul"
 out.writeline "msiexec.exe /p c:\windows\temp\amd64\KB3190029-amd64-Agent.msp"
 out.writeline "ping 1.1.1.1 -n 70 /w 1000 " & chr(62) & " nul"
 'create VBscript that will register any other management groups and add line to commandfile to execute the VBscript
 for x = 0 to size
  if arrMGGroups(x) <> PrimaryMG then
   VBfilename = outfolder & "\AddMGgroup" & x
   'create unique VBfile to register extra MGgroup
   AddMGgroup arrMGGroups(x), arrMGservers(x), arrMGPorts(x), VBfilename
   out.writeline "cscript.exe " & VBfilename & ".vbs"
   out.writeline "ping 1.1.1.1 -n 20 /w 1000 " & chr(62) & " nul"
  end if
 next
 out.writeline "rd /s /q " & OutFolder
 out.close
 wscript.sleep(1000)
 'rename text file to .CMD
 CMDFile = Replace(right(Outputfile,len(Outputfile) - instrrev(Outputfile,"\")),"txt", "cmd")
 oShell.run "CMD /C rename " & Outputfile & " " & CMDFile, 0, true
 wscript.echo "Renamed " & outputfile & " to " & CMDFile
 delay = 3
 oShell.run "cmd /c %windir%\system32\schtasks.exe /create /RU SYSTEM /RL HIGHEST /V1 /Z /F /sc ONCE /st " & formatdatetime(dateadd("N",delay,now()),VBShortTime) & " /TN SCOMAgentUpdate /tr " & OutFolder & "\" & CMDFile
 info = "OpsNoAPM.cmd: created OpsNoAPM.cmd - it's scheduled to run at " & formatdatetime(dateadd("N",delay,now()),VBShortTime)
 oShell.LogEvent 4, info
 wscript.echo info
Else
 info = "OpsNoAPM.cmd: no registry entry found with the install location of the SCOM agent"
 oShell.LogEvent 4, info
 wscript.echo info
end If
Set oShell = Nothing
set out = Nothing
set fs = Nothing


'subroutine to create VBscript file to register an extra management group
sub AddMGgroup (MGName, MSNAme, MGPort,VBfile)
 dim VBf
 Set VBf = fs.CreateTextFile(VBfile & ".txt")
 VBf.writeline "Set objMSConfig = CreateObject(""AgentConfigManager.MgmtSvcCfg"")"
 VBf.writeline "Call objMSConfig.AddManagementGroup(" & MGName & "," & MSNAme & "," & MGPort & ")"
 VBf.writeline "If Err.number <> 0 Then"
 VBf.writeline " wscript.echo (""Failed to add " & MGName & """)"
 VBf.writeline "Else"
 VBf.writeline " Set oShell = WScript.CreateObject(""WScript.Shell"")"
 VBf.writeline " Set oAPI = CreateObject(""MOM.ScriptAPI"")"
 VBf.writeline " set oShellEnv = oShell.Environment(""Process"")"
 VBf.writeline " computerName = oShellEnv(""ComputerName"")"
 VBf.writeline " strCommand = ""cmd /c net stop HealthService & cmd /c net start HealthService"""
 VBf.writeline " Set objWMIService = GetObject(""winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2"")"
 VBf.writeline " Set objProcess = objWMIService.Get(""Win32_Process"")"
 VBf.writeline " Set objProgram = objProcess.Methods_( _"
 VBf.writeline " ""Create"").InParameters.SpawnInstance_"
 VBf.writeline " objProgram.CommandLine = strCommand"
 VBf.writeline " Set strShell = objWMIService.ExecMethod(""Win32_Process"", ""Create"", objProgram)"
 VBf.writeline " wscript.echo (""Management Group " + MGName + " has been added"")"
 VBf.writeline " WScript.echo ""Restarting SCOM Health Service on "" & computerName"
 VBf.writeline "End If"
 VBf.close
 vbFile1 = right(VBFile,len(VBFile) - instrrev(VBFile,"\")) & ".vbs"
 wscript.echo "CMD /C rename " & VBfile & ".txt " & VBfile1
 'Rename text file to .VBS
 oShell.run "CMD /C rename " & VBfile & ".txt " & VBfile1 , 0, true
end sub


Tuesday, October 3, 2017

SCOM 2012/2016 automated subscribers verification and cleanup

in our SCOM environment, we ended up with a lot of email subscribers - for a lot of users we created a subscriber. and deleting is not possible unless the user is removed from all subscriptions.
so I released already a script to find a subscriber and then remove it from all subscriptions and then delete the subscriber.




this script takes it a step further. it can be used for cleanup of the subscribers (email only).
it will enumerate through all subscribers, check if it has an SMTP subscription, and then try to find the AD user based on the email address (and check if the found user is disabled)
it will then ask you if you want to delete the user (email address)


if you select YES, it will enumerate all subscriptions, and try to find the subscriber (by GUID) and remove it from every subscription. if removal fails with the error "requires at least one recipient" then the entire subscription is deleted. (maybe it would be better to ask first if you want to delete it - but that's up to you- that is pretty easy to implement)








$error.clear()
$ManagementServer = "FQDN"

$Module = get-module|where {$_.Name -match "OperationsManager"}
if (!($Module)){
    Write-Host "Import OperationsManager Module"
    import-module OperationsManager
}

$Module = get-module|where {$_.Name -match "ActiveDirectory"}
if (!($Module)){
    Write-Host "Import ActiveDirectory Module"
    import-module ActiveDirectory
}

Write-Host "Connecting to SCOM Management Group"
$ManagementServer = New-Object Microsoft.EnterpriseManagement.ManagementGroup($ManagementServer)
#popup window object
$YesNo = new-object -comobject wscript.shell


Function DeleteSubscriber
{
    Param ([string]$SubID,
                 [String]$user)
   
    $Subscriptions = Get-SCOMNotificationSubscription
    foreach ($subscription in $Subscriptions)
    {
  $SubscriptionName = $subscription.DisplayName
  $Recipient = $Null
  foreach ($rec in $subscription.ToRecipients)
  {
   If ($rec.id -match $SubID)
   {
   $SubscriptionName + " -- " + $subscription.Enabled.ToString()
   $Recipient = $rec
   }
  }


  #we first have to exit the foreach loop above, otherwise it fails if we delete the user.
  if ($Recipient -ne $Null)
  {
   try
   {
    $subscription.ToRecipients.Remove($Recipient)
    $subscription.Update()
    "deleted $user from" + $subscription.DisplayName
   }
   catch
   {
    if ($error[0].exception -match "requires at least one recipient")
    {
     Get-SCOMNotificationSubscription -Name $subscription.name | Remove-SCOMNotificationSubscription
     "deleted subscription " + $subscription.DisplayName + " because $user was the only recipient"
    }
   }
  }
 }

    "now we delete the subscriber $user with ID " + $SubID
    Get-SCOMNotificationSubscriber -id $SubID | Remove-SCOMNotificationSubscriber
    if ( $error[0].exception -match "Please call ManagementGroup.Reconnect()")
    {
        $ManagementServer.Reconnect()
  "Subscriber $user was not deleted because it still is linked to a subscribtion"
    }
}#END of Function



#Main - let's enumerate all subscribers
$subscribers = Get-SCOMNotificationSubscriber
foreach ($subscriber in $subscribers)
{
    foreach ($protocol in $subscriber.devices)
    {
        if ($protocol.Protocol -eq "Smtp")
        {
            $email = $protocol.Address.tostring()
             $filt = 'mail -eq "' + $email + '"'
             $result = Get-ADUser -filter $filt
            if ($result.enabled -eq $False -and $result -ne $null)
            {

                #the found AD account is disabled, so let's ask the question
                $usr = $protocol.Address
                $subscriber.Name + " -- " + $usr
                $intAnswer = $YesNo.popup("Do you want to delete $usr", 0,"Delete User",4)
                If ($intAnswer -eq 6)
                { #YES delete the user
                    DeleteSubscriber -SubID $subscriber.id -user $usr
                }
                else
                { #No
                    $protocol.Address + " will not be deleted"
                }
           }
           
        }
    }
}







Monday, September 25, 2017

SCOM - remove subscriber from all subscriptions and delete subscriber

when trying to delete a subscriber, you may get an error that the subscriber is still in use. but there is no quick and simple way to view in the SCOM console the memberships for the subscriber....


but there is a simple powershell script that will provide that information.


let's connect to SCOM (I prefer to use the Powershell_ISE console.... ) first:


$MG= "YourManagementServerFQDN"
$Module = get-module|where {$_.Name -match "OperationsManager"}
if (!($Module)){
    Write-Host "Import OperationsManager Module"
    import-module OperationsManager
}
Write-Host "Connecting to SCOM Management Group"
$ManagementServer = New-Object Microsoft.EnterpriseManagement.ManagementGroup($MG)



the next step is to get all subscriptions, and then enumerate the members (and only show those that match the member you are looking for


$a = Get-SCOMNotificationSubscription
foreach ($b in $a)
{
       $ns = $b.DisplayName
       $b.ToRecipients | foreach { If ($_.Name -match "SubscriberName") { Write-Host $ns " --"                   $B.Enabled.ToString() } }
}


when you create a subscriber it automatically picks your domainname \username so in our environment I only search for the username - and I just test it first in the console in the subscriber pane.


but... powershell is to make life easy - so can even be a lot more simple.
In the script below it will find any subscription where the subscribername is found, and it then removes that subscriber from the ToRecipients, and updates the subscription.
and the last step is to remove the subscriber once it has been removed from all subscriptions !!


$a = Get-SCOMNotificationSubscription
$user = "SubscriberName"
foreach ($b in $a)
{
 $ns = $b.DisplayName
 $d = $Null
 foreach ($c in $b.ToRecipients)
 {
   If ($c.Name -match $user)
    {
        Write-Host $ns " -- " $B.Enabled.ToString()
        $d = $C
    }
 }
 if ($d -ne $null)
 {
    $b.ToRecipients.Remove($d)
    $b.Update()
 }
}
write-host "now we delete the subscriber $user"
$del = Get-SCOMNotificationSubscriber -Name "*$user"
Remove-SCOMNotificationSubscriber $del



That's it !







Monday, May 2, 2016

System Center Service Manager 2012 R2 CSV sync by using Import-SCSMClassInstance

Problem: import-SCSMinstance imports objects that are in a specified CSV file, but... it does not remove "obsolete" objects. - I want to really sync - load what is missing, and remove what is obsolete

 if you drop all objects and then import them again, objects get a new GUID, so you lose history for the objects that are imported again, so this is not a preferred solution.

here is how you truly sync (mirror) the objects - this example is a simple class only with displaynames of groups:

Create your CSV file, this should contain all the objects that should be in your class
in my example, I load them from a SQL database:

$query = "select last_name from ca_contact where contact_type=2308"
$connection = new-object system.data.sqlclient.sqlconnection($StrConn);
$adapter = new-object system.data.sqlclient.sqldataadapter ($query, $connection)
$set = new-object system.data.dataset
$adapter.Fill($set)
$table = new-object system.data.datatable
$table = $set.Tables[0]

this is collecting some group names into $table
then create the CSV file and I add the values also to array $csv (I  run a "-contains" against this array - which is not possible against the $table variable)

$csvfile = "C:\SCSMimport\USDGroups.csv"
$csv=@()
Foreach ($R in ($table))
{
 
    Add-Content $csvfile ($R.last_name)
     $csv += $name
}

get all your current objects loaded in SCSM:
$GrpClass = Get-SCSMClass -Name USDgroups
$all = Get-SCSMClassInstance -Class $GrpClass

and now we compare $all against the array that contains anything that should be in the group.
 
foreach ($obj in $all)
{    if ($csv -notcontains $obj.DisplayName)
    {        "obsolete group : " + $obj.DisplayName
        Remove-SCSMClassInstance -Instance $obj
     }
}
 


So.. anything not in the CSV but still loaded in the class in SCSM is now removed from SCSM

and then the last step to ensure new objects are added:
Import-SCSMInstance -DataFileName $csvfile -FormatFileName 'C:\tools\SCSMimport\USDGroups.xml'

 that's it !