vCenter Alarms export/import revisited and fixed

UPDATE 2 Feb 2022:

Unfortunately, due to changes in vCenter 7.0U2 and U3 the below procedure does not work flawlessly anymore. Thanks to my colleague/customer Willem for noticing this and providing the details! So, what changed in the latest version of vSphere 7?

  • New alarms with special characters > fixed with a function to filter them
  • Description field to long to import > we truncate them now to 254 chars at import
  • Export files are now having a descriptive name

All changes in the code are listed below in green!

Exporting and importing vCenter Alarms has some extensive history within the VMware community. There was a continued demand for easy to use and trustworthy tools/scripts. The two main sources I got in contact with were the VMware KB and this thread on the community, and not to forget this great tool from the vCenter 4/5 era.

Recently I was asked by a valued colleague (thanks Willem!) to help him with his challenge to find and use a tool/script that could consistently export Alarms from one source vCenter and import it to several other target vCenters. Although I once graduated as a programmer I must admit I’m not that agile anymore on the subject. Nevertheless I took on the challenge, did some investigation and dug into the programming details of PowerCLI. We tried the referenced existing scripts but they all failed to do the job properly on more recent versions (6.7/7.0) of vCenter. Main issues encountered were:

  • Double results > vCenter connections
  • Differences between new and existing Alarms > SystemName
  • Long Alarm names fail to import > max 80 chars
  • Overwriting (modified) Alarms > delete before import
  • vCenter specific Alarms > exclude them

From the script review I decided the export/import scripts from the community were best suited to continue my efforts to solve the issues.

Let’s start of with the export part of the solution. This is the easy one and already ran without major issues. The only pieces added are the exclusion of vCenter specific Alarms (short investigation by Willem showed these are all containing the word “Exhaustion”) and disconnecting from vCenters which otherwise caused double results.

function Replace-SpecialChars {
    $SpecialChars = '[#?\/{:}]'
    $Replacement  = '-'
    $InputString -replace $SpecialChars,$Replacement

function ExportAlarm() {
   if( -not (Test-Path -Path $exportFolder -PathType Container) ) {
      mkdir -Path $exportFolder}
 Connect-VIServer $serverToExportFrom -User 'administrator' -Password 'VMware1!'
 # Exclude vCenter specific Alarms containing "Exhaustion"in the name AND matching the filter
   $alarmToExport = Get-AlarmDefinition $alarmToExportName | ? {$ -notmatch "Exhaustion"}
   $alarmToExport | % {
      $a = Get-View -Id $_.Id
      $CustomAlarmName = $alarmprefix + ' ' + (Replace-SpecialChars -InputString $
     $ = $CustomAlarmName
     $ShortCustomAlarmName=$CustomAlarmName[1..84] -join ''
     $a.Info | Export-Clixml -Path ($exportFolder + $ShortCustomAlarmName  + ".xml") -Depth ( [System.Int32]::MaxValue )}
 $serverToExportFrom = ""
 $alarmToExportName = "Alarmtest"
 $exportFolder = "C:\Users\Administrator\alarms\"
 $alarmToExportName = "*"
 Disconnect-VIServer * -Confirm:$False -Force:$True

The result of this script is a bunch of XML-files in the specified folder containing all alarmToExportName-filtered alarms except the vCenter-specific alarms. These files will be the source for the import-script.

The import-script contains the most critical changes for where things went horribly wrong. Most of the listed issues are solved here in shown in Orange.

function ImportAlarm {
 Connect-VIServer $serverToImportTo -User 'administrator@vsphere.local'-Password 'VMware1!'

# Remove all but vCenter-specific Alarms from the target vCenter and wait for 15 seconds before we bulk-import the new Alarms
 Get-AlarmDefinition | ? {$ -notmatch "Exhaustion"} |Remove-AlarmDefinition -Confirm:$false
 sleep 15

 Get-ChildItem -Path ($exportFolder + "*.xml") | % {
 $deserializedAlarmInfo = Import-Clixml -Path $_.FullName
  # We need to limit the Alarm-name to 80 characters for it to import correctly
  $deserializedAlarmInfo.Name = ($deserializedAlarmInfo.Name[0..76] -join "")
  $deserializedAlarmInfo.Description = ($deserializedAlarmInfo.Description[0..254] -join "")
  $importedAlarmInfo = ConvertFromDeserialized( $deserializedAlarmInfo )
  $entity = Get-Folder -NoRecursion 
  $alarmManager = Get-View -Id 'AlarmManager-AlarmManager'
  $alarmManager.CreateAlarm($entity.Id, $importedAlarmInfo)}
 # This function converts a Powershell object deserialized from xml (with Import-Clixml) to its original type (that has been previously serialized with Export-Clixml)
 function ConvertFromDeserialized {
 if($deserializedObject -eq $null){return $null}
 $deserializedTypeName = ($deserializedObject | Get-Member -Force | where { $_.Name -eq "psbase" } ).TypeName;
 if($deserializedTypeName.StartsWith("Deserialized.")) {
   $originalTypeName = $deserializedTypeName.Replace("Deserialized.", "")
   $result = New-Object -TypeName $originalTypeName
   $resultType = $result.GetType()
      $result = [Enum]::Parse($resultType, $deserializedObject, $true)
      return $result}
 $deserializedObject | Get-Member | % {
# exclude "SystemName" entries from the XML-import as these will fail during the CreateAlarm call
   if($_.MemberType -eq "Property" -and $_.Name -ne "SystemName") {
     $resultProperty = $resultType.GetProperty($_.Name)
     if($resultProperty.CanWrite ){
        $propertyValue = ( Invoke-Expression ('$deserializedObject.' + $_.Name) | % { ConvertFromDeserialized( $_ ) } )
        if($propertyValue -and $resultProperty.PropertyType.IsArray ) {
              $elementTypeName = $resultProperty.PropertyType.AssemblyQualifiedName.Replace("[]", "")
              $elementType = [System.Type]::GetType($elementTypeName)
              $array = [System.Array]::CreateInstance($elementType, $propertyValue.Count)
              for($i = 0; $i -lt $array.Length; $i++){
                  $array[$i] = $propertyValue[$i]}
                  $propertyValue = $array
              } else {
                  $elementTypeName = $resultProperty.PropertyType.AssemblyQualifiedName.Replace("[]", "")
                  $elementType = [System.Type]::GetType($elementTypeName)
                  $array = [System.Array]::CreateInstance($elementType, 1)
                  $array[0] = $propertyValue
                  $propertyValue = $array}}
           $resultProperty.SetValue($result, $propertyValue, $null)}}}
     } else {
       $result = $deserializedObject}  
 return $result     
 $serverToImportTo = "vcsa1.vmw.local"
 $exportFolder = "C:\Users\Administrator\alarms\"
 Disconnect-VIServer * -Confirm:$false -Force:$true

With the above you should now be able to consistently export and import vCenter Alarms in your environment. And, of course, you can add a lot of additional features to enhance the script with more security, input-boxes, filename-conversions and prefix additions. I leave it up to you to figure that out.

Marco Baaijen

4 Responses

  1. Looks like there are issues with both the length of $deserializedAlarmInfo.Name and $deserializedAlarmInfo.Description. If the description is too long it will also fail but I am yet to work out the maximum length.

  2. Seriously, why isn’t import, export, and copy alarm functionality just built into the vSphere web client at this point? This is pathetic that admins should have to jump through these kind of hoops with a product as mature as vCenter is supposed to be. Not everyone is a competent powershell scripter and they shouldn’t have to be for these kind actions.

    • Hi Mike, I agree that this could have been standard functionality by now, but VMware has chosen to leave the implementation of this to the community, like they do in more use-cases. Having said that, VMware is actively working on the vCenter-Profiles feature with support for Alarms. The UI-feature will however only become available in VMC/vSphere+. For onprem it will still be API-based only.

Leave a Reply

Your email address will not be published. Required fields are marked *

Post comment