How to unpublish a published content type if the published content type no longer exists

One of my colleagues (Steffi Buchner) recently got a case where a customer deleted a content type hub and later noticed that some of the content types provided by this hub were already published.

The problem is that the information about which content types are published is stored in the TermStore database outside of the content type hub – leading to orphan published content types.

If you create a new site collection now you will notice that the content types earlier published by the now deleted content type hub show up as subscribed content types.

How can you get rid of such an orphan content type? This is now an hen and egg problem: to unpublish a content type you need to go to the content type hub, select the content type and unpublish it. As the content type hub no longer exists it is not possible use this option. The same applies if only the content type in the content type hub has been deleted without unpublishing it.

Note: there is no extra check in the content type hub which would prevent you from deleting a content type which is already published or which would force it to be unpublished. The same applies to deleting a content type hub. You can delete it even if there are still content types being published from the content type hub. 

After some research my colleague identified a nice workaround: the API used to unpublish a content type does not really bother about whether it is being called on the content type object has been retrieved from the content type hub or in one of the subscribed site collections. The workaround requires an existing content type hub. So if you deleted the hub you would need to create a new one to apply the workaround – after all content types are unpublished you can delete it again if required.

Below is the powershell script that can be used to unpublish orphan published content types:

Add-PSSnapIn Microsoft.SharePoint.PowerShell

if ($args.Count -ne 3)
{
   Write-Host "UnpublishContentTypes.ps1 <web url> <content type hub url> <unpublish>"
   exit
}

$webUrl = $args[0]
$ctUrl = $args[1]
$unpublish = $args[2]

$web = Get-SPWeb $webUrl
$ctsite = Get-SPSite $ctUrl
$ctPublisher = New-Object Microsoft.SharePoint.Taxonomy.ContentTypeSync.ContentTypePublisher($ctsite)

foreach($ct in $web.ContentTypes)
{
    if ( $ct.XmlDocuments -like "*SharedContentType*")
    {
        try
        { 
            if (-not $ctsite.RootWeb.ContentTypes[$ct.Name])
            {
                Write-Host "Found orphan Shared ContentType $($ct.Name) – ($($ct.ID))"
             
                if($unpublish)
                {
                    $ctPublisher.UnPublish($ct)
                    Write-Host "$($ct.Name) – ($($ct.ID)) UnPublished successfully"
                }
            }
        }
        catch
        {
            Write-Host "Error unpublishing ContentType $($ct.Name) – ($($ct.ID)): $error[0]"
        }
    }
}

if($unpublish)
{
    Start-SPTimerJob MetadataSubscriberTimerJob
}

9 Comments


  1. One of my colleagues (Steffi Buchner) recently got a case where a customer deleted a content type hub

    Reply

  2. oh my… using $args for input handling… that's bad

    you should use
    param (
    [Microsoft.SharePoint.Powershell.SPWebPipeBind]$Web,
    [Microsoft.SharePoint.Powershell.SPSitePipeBind]$PublishingSiteCollection
    [switch]$Unpublish
    )

    Use SPWebPipeBind and SPSitePipeBind to let the user pass the url or an SPSite/SPWeb object from the pipeline (use $spWeb = $Web.Read() to get the actual SPWeb)

    Reply

  3. Hi Piotr,

    excellent comment! You are correct, that would be a much cleaner implementation.

    Cheers,
    Stefan

    Reply

  4. how do i get this script to run? it just askes me to save?? im not very good with power shell
    i deleted a content type from the hub without unpublishing it first

    Reply

    1. Hi Alz,
      you need to add the script to a text file named something with file extension .ps1 – e.g. myscript.ps1
      Then open the SharePoint management shell and run this script by going to the directory where the script file is located and execute it by adding “.\” in front of the command.
      E.g.:
      PS> .\myscript.ps1
      Cheers,
      Stefan

      Reply

  5. is this applicable for sharepoint online… what should be the in the below code
    {
    Write-Host “UnpublishContentTypes.ps1 ”
    exit

    Reply

  6. Any chance some one knows how to get rid of orphaned content types in SharePoint online? have a client and they deleted the content types without unpublishing them from the hub. – now they keep coming back.

    Reply

  7. Hi Stefan,
    I know this is an older post (came across it while googling the symptoms of my problem), but maybe you’ll be open to discussing my dilemma, which is partly related to this.
    The point is that I need to get rid of unused custom content types (CTs) published through Content Type Hub (CTH) on some site collections (SCs).
    First thing to clarify: I have a farm that was migrated from the SharePoint 2013 platform to SharePoint 2019, via an intermediate farm based on SharePoint 2016, with db attach method.
    Content databases were migrated, but databases of all used service applications were not.
    It is obvious that they (my custom CTs) cannot be simply removed from subscriber SCs, they must first be Unpublished so that they can be further worked with (ie can be deleted).
    I also have original CTH available, but with a certain difference: it is not connected to the corresponding Managed metadata service application (MMSA).
    MMSA was created new from scratch and is currently configured without its CTH.
    However, my original CTH has references to both the original termstore and the MMSA, through items in its hidden Shared Packages list (/Lists/PackageList/AllItems.aspx).
    If I connect this CTH to the current MMSA, will I be able to do Unpublish and then my custom CTs remove from the target (so-called spoke) SCs? Won’t there be some ID mismatch with that I will mess-up my farm?
    The script shown here assumes that a new (empty) CTH is passed to it, and the unpublish method ($ctPublisher.UnPublish($ct)) does the rest. I don’t believe that it doesn’t need anything for that, because the new CTH supplied, in the second argument of script, is “empty” regarding CTs data.
    However, does it not rely on the existence of at least some data about the given CTs?
    I mean, e.g. from the database of the corresponding MMSA (there is ECMPackage table, containing informations on all CTs published in farm, see https://blog.collabware.com/unpublish-orphaned-content-types-in-sharepoint).
    But because I have new MMSA, that db table is empty. So I have some worries about joining the old CTH with new MMSA.
    How can I proceed, what is correct approach?

    I kindly ask for your opinion and thank you in advance for it.

    Reply

    1. Hi eLKey,
      there are various aspects to consider and it is hard to answer that without some research.
      In this case I would recommend to open a support case with Microsoft to get a correct answer.
      Cheers,
      Stefan

      Reply

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.