netcmd: domain: move schemaupgrade command to domain/schemaupgrade.py
authorRob van der Linde <rob@catalyst.net.nz>
Fri, 31 Mar 2023 00:34:20 +0000 (13:34 +1300)
committerAndrew Bartlett <abartlet@samba.org>
Fri, 31 Mar 2023 07:25:32 +0000 (07:25 +0000)
Signed-off-by: Rob van der Linde <rob@catalyst.net.nz>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
python/samba/netcmd/domain/__init__.py
python/samba/netcmd/domain/schemaupgrade.py [new file with mode: 0644]

index 6b0383a756fb4e4578e248e4b4ff4427a4f7fed0..19ef1b05c4200fedd448febc75abe68274b7e7ee 100644 (file)
 
 import samba.getopt as options
 import ldb
-import os
 import ctypes
-import tempfile
-import subprocess
 import time
-import shutil
 from samba import ntstatus
 from samba import NTSTATUSError
 from samba import werror
@@ -49,12 +45,9 @@ from samba.netcmd import (
     SuperCommand,
     Option
 )
-from samba.netcmd.fsmo import get_fsmo_roleowner
 from samba import string_to_byte_array
 from samba import is_ad_dc_built
 
-from samba.provision import setup_path
-
 from samba.trust_utils import CreateTrustedDomainRelax
 
 from .backup import cmd_domain_backup
@@ -72,6 +65,7 @@ from .level import cmd_domain_level
 from .passwordsettings import cmd_domain_passwordsettings
 from .provision import cmd_domain_provision
 from .samba3upgrade import cmd_domain_samba3upgrade
+from .schemaupgrade import cmd_domain_schema_upgrade
 
 
 class LocalDCCredentialsOptions(options.CredentialsOptions):
@@ -2459,320 +2453,6 @@ class cmd_domain_tombstones(SuperCommand):
     subcommands["expunge"] = cmd_domain_tombstones_expunge()
 
 
-class ldif_schema_update:
-    """Helper class for applying LDIF schema updates"""
-
-    def __init__(self):
-        self.is_defunct = False
-        self.unknown_oid = None
-        self.dn = None
-        self.ldif = ""
-
-    def can_ignore_failure(self, error):
-        """Checks if we can safely ignore failure to apply an LDIF update"""
-        (num, errstr) = error.args
-
-        # Microsoft has marked objects as defunct that Samba doesn't know about
-        if num == ldb.ERR_NO_SUCH_OBJECT and self.is_defunct:
-            print("Defunct object %s doesn't exist, skipping" % self.dn)
-            return True
-        elif self.unknown_oid is not None:
-            print("Skipping unknown OID %s for object %s" % (self.unknown_oid, self.dn))
-            return True
-
-        return False
-
-    def apply(self, samdb):
-        """Applies a single LDIF update to the schema"""
-
-        try:
-            try:
-                samdb.modify_ldif(self.ldif, controls=['relax:0'])
-            except ldb.LdbError as e:
-                if e.args[0] == ldb.ERR_INVALID_ATTRIBUTE_SYNTAX:
-
-                    # REFRESH after a failed change
-
-                    # Otherwise the OID-to-attribute mapping in
-                    # _apply_updates_in_file() won't work, because it
-                    # can't lookup the new OID in the schema
-                    samdb.set_schema_update_now()
-
-                    samdb.modify_ldif(self.ldif, controls=['relax:0'])
-                else:
-                    raise
-        except ldb.LdbError as e:
-            if self.can_ignore_failure(e):
-                return 0
-            else:
-                print("Exception: %s" % e)
-                print("Encountered while trying to apply the following LDIF")
-                print("----------------------------------------------------")
-                print("%s" % self.ldif)
-
-                raise
-
-        return 1
-
-
-class cmd_domain_schema_upgrade(Command):
-    """Domain schema upgrading"""
-
-    synopsis = "%prog [options]"
-
-    takes_optiongroups = {
-        "sambaopts": options.SambaOptions,
-        "versionopts": options.VersionOptions,
-        "credopts": options.CredentialsOptions,
-    }
-
-    takes_options = [
-        Option("-H", "--URL", help="LDB URL for database or target server", type=str,
-               metavar="URL", dest="H"),
-        Option("-q", "--quiet", help="Be quiet", action="store_true"),  # unused
-        Option("-v", "--verbose", help="Be verbose", action="store_true"),
-        Option("--schema", type="choice", metavar="SCHEMA",
-               choices=["2012", "2012_R2", "2016", "2019"],
-               help="The schema file to upgrade to. Default is (Windows) 2019.",
-               default="2019"),
-        Option("--ldf-file", type=str, default=None,
-               help="Just apply the schema updates in the adprep/.LDF file(s) specified"),
-        Option("--base-dir", type=str, default=None,
-               help="Location of ldf files Default is ${SETUPDIR}/adprep.")
-    ]
-
-    def _apply_updates_in_file(self, samdb, ldif_file):
-        """
-        Applies a series of updates specified in an .LDIF file. The .LDIF file
-        is based on the adprep Schema updates provided by Microsoft.
-        """
-        count = 0
-        ldif_op = ldif_schema_update()
-
-        # parse the file line by line and work out each update operation to apply
-        for line in ldif_file:
-
-            line = line.rstrip()
-
-            # the operations in the .LDIF file are separated by blank lines. If
-            # we hit a blank line, try to apply the update we've parsed so far
-            if line == '':
-
-                # keep going if we haven't parsed anything yet
-                if ldif_op.ldif == '':
-                    continue
-
-                # Apply the individual change
-                count += ldif_op.apply(samdb)
-
-                # start storing the next operation from scratch again
-                ldif_op = ldif_schema_update()
-                continue
-
-            # replace the placeholder domain name in the .ldif file with the real domain
-            if line.upper().endswith('DC=X'):
-                line = line[:-len('DC=X')] + str(samdb.get_default_basedn())
-            elif line.upper().endswith('CN=X'):
-                line = line[:-len('CN=X')] + str(samdb.get_default_basedn())
-
-            values = line.split(':')
-
-            if values[0].lower() == 'dn':
-                ldif_op.dn = values[1].strip()
-
-            # replace the Windows-specific operation with the Samba one
-            if values[0].lower() == 'changetype':
-                line = line.lower().replace(': ntdsschemaadd',
-                                            ': add')
-                line = line.lower().replace(': ntdsschemamodify',
-                                            ': modify')
-                line = line.lower().replace(': ntdsschemamodrdn',
-                                            ': modrdn')
-                line = line.lower().replace(': ntdsschemadelete',
-                                            ': delete')
-
-            if values[0].lower() in ['rdnattid', 'subclassof',
-                                     'systemposssuperiors',
-                                     'systemmaycontain',
-                                     'systemauxiliaryclass']:
-                _, value = values
-
-                # The Microsoft updates contain some OIDs we don't recognize.
-                # Query the DB to see if we can work out the OID this update is
-                # referring to. If we find a match, then replace the OID with
-                # the ldapDisplayname
-                if '.' in value:
-                    res = samdb.search(base=samdb.get_schema_basedn(),
-                                       expression="(|(attributeId=%s)(governsId=%s))" %
-                                       (value, value),
-                                       attrs=['ldapDisplayName'])
-
-                    if len(res) != 1:
-                        ldif_op.unknown_oid = value
-                    else:
-                        display_name = str(res[0]['ldapDisplayName'][0])
-                        line = line.replace(value, ' ' + display_name)
-
-            # Microsoft has marked objects as defunct that Samba doesn't know about
-            if values[0].lower() == 'isdefunct' and values[1].strip().lower() == 'true':
-                ldif_op.is_defunct = True
-
-            # Samba has added the showInAdvancedViewOnly attribute to all objects,
-            # so rather than doing an add, we need to do a replace
-            if values[0].lower() == 'add' and values[1].strip().lower() == 'showinadvancedviewonly':
-                line = 'replace: showInAdvancedViewOnly'
-
-            # Add the line to the current LDIF operation (including the newline
-            # we stripped off at the start of the loop)
-            ldif_op.ldif += line + '\n'
-
-        return count
-
-    def _apply_update(self, samdb, update_file, base_dir):
-        """Wrapper function for parsing an LDIF file and applying the updates"""
-
-        print("Applying %s updates..." % update_file)
-
-        ldif_file = None
-        try:
-            ldif_file = open(os.path.join(base_dir, update_file))
-
-            count = self._apply_updates_in_file(samdb, ldif_file)
-
-        finally:
-            if ldif_file:
-                ldif_file.close()
-
-        print("%u changes applied" % count)
-
-        return count
-
-    def run(self, **kwargs):
-        try:
-            from samba.ms_schema_markdown import read_ms_markdown
-        except ImportError as e:
-            self.outf.write("Exception in importing markdown: %s" % e)
-            raise CommandError('Failed to import module markdown')
-        from samba.schema import Schema
-
-        updates_allowed_overridden = False
-        sambaopts = kwargs.get("sambaopts")
-        credopts = kwargs.get("credopts")
-        lp = sambaopts.get_loadparm()
-        creds = credopts.get_credentials(lp)
-        H = kwargs.get("H")
-        target_schema = kwargs.get("schema")
-        ldf_files = kwargs.get("ldf_file")
-        base_dir = kwargs.get("base_dir")
-
-        temp_folder = None
-
-        samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
-
-        # we're not going to get far if the config doesn't allow schema updates
-        if lp.get("dsdb:schema update allowed") is None:
-            lp.set("dsdb:schema update allowed", "yes")
-            print("Temporarily overriding 'dsdb:schema update allowed' setting")
-            updates_allowed_overridden = True
-
-        own_dn = ldb.Dn(samdb, samdb.get_dsServiceName())
-        master = get_fsmo_roleowner(samdb, str(samdb.get_schema_basedn()),
-                                    'schema')
-        if own_dn != master:
-            raise CommandError("This server is not the schema master.")
-
-        # if specific LDIF files were specified, just apply them
-        if ldf_files:
-            schema_updates = ldf_files.split(",")
-        else:
-            schema_updates = []
-
-            # work out the version of the target schema we're upgrading to
-            end = Schema.get_version(target_schema)
-
-            # work out the version of the schema we're currently using
-            res = samdb.search(base=samdb.get_schema_basedn(),
-                               scope=ldb.SCOPE_BASE, attrs=['objectVersion'])
-
-            if len(res) != 1:
-                raise CommandError('Could not determine current schema version')
-            start = int(res[0]['objectVersion'][0]) + 1
-
-            diff_dir = setup_path("adprep/WindowsServerDocs")
-            if base_dir is None:
-                # Read from the Schema-Updates.md file
-                temp_folder = tempfile.mkdtemp()
-
-                update_file = setup_path("adprep/WindowsServerDocs/Schema-Updates.md")
-
-                try:
-                    read_ms_markdown(update_file, temp_folder)
-                except Exception as e:
-                    print("Exception in markdown parsing: %s" % e)
-                    shutil.rmtree(temp_folder)
-                    raise CommandError('Failed to upgrade schema')
-
-                base_dir = temp_folder
-
-            for version in range(start, end + 1):
-                update = 'Sch%d.ldf' % version
-                schema_updates.append(update)
-
-                # Apply patches if we parsed the Schema-Updates.md file
-                diff = os.path.abspath(os.path.join(diff_dir, update + '.diff'))
-                if temp_folder and os.path.exists(diff):
-                    try:
-                        p = subprocess.Popen(['patch', update, '-i', diff],
-                                             stdout=subprocess.PIPE,
-                                             stderr=subprocess.PIPE, cwd=temp_folder)
-                    except (OSError, IOError):
-                        shutil.rmtree(temp_folder)
-                        raise CommandError("Failed to upgrade schema. "
-                                           "Is '/usr/bin/patch' missing?")
-
-                    stdout, stderr = p.communicate()
-
-                    if p.returncode:
-                        print("Exception in patch: %s\n%s" % (stdout, stderr))
-                        shutil.rmtree(temp_folder)
-                        raise CommandError('Failed to upgrade schema')
-
-                    print("Patched %s using %s" % (update, diff))
-
-        if base_dir is None:
-            base_dir = setup_path("adprep")
-
-        samdb.transaction_start()
-        count = 0
-        error_encountered = False
-
-        try:
-            # Apply the schema updates needed to move to the new schema version
-            for ldif_file in schema_updates:
-                count += self._apply_update(samdb, ldif_file, base_dir)
-
-            if count > 0:
-                samdb.transaction_commit()
-                print("Schema successfully updated")
-            else:
-                print("No changes applied to schema")
-                samdb.transaction_cancel()
-        except Exception as e:
-            print("Exception: %s" % e)
-            print("Error encountered, aborting schema upgrade")
-            samdb.transaction_cancel()
-            error_encountered = True
-
-        if updates_allowed_overridden:
-            lp.set("dsdb:schema update allowed", "no")
-
-        if temp_folder:
-            shutil.rmtree(temp_folder)
-
-        if error_encountered:
-            raise CommandError('Failed to upgrade schema')
-
-
 class cmd_domain(SuperCommand):
     """Domain management."""
 
diff --git a/python/samba/netcmd/domain/schemaupgrade.py b/python/samba/netcmd/domain/schemaupgrade.py
new file mode 100644 (file)
index 0000000..1d67ab5
--- /dev/null
@@ -0,0 +1,350 @@
+# domain management - domain schemaupgrade
+#
+# Copyright Matthias Dieter Wallnoefer 2009
+# Copyright Andrew Kroeger 2009
+# Copyright Jelmer Vernooij 2007-2012
+# Copyright Giampaolo Lauria 2011
+# Copyright Matthieu Patou <mat@matws.net> 2011
+# Copyright Andrew Bartlett 2008-2015
+# Copyright Stefan Metzmacher 2012
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+import os
+import shutil
+import subprocess
+import tempfile
+
+import ldb
+import samba.getopt as options
+from samba.auth import system_session
+from samba.netcmd import Command, CommandError, Option
+from samba.netcmd.fsmo import get_fsmo_roleowner
+from samba.provision import setup_path
+from samba.samdb import SamDB
+
+
+class ldif_schema_update:
+    """Helper class for applying LDIF schema updates"""
+
+    def __init__(self):
+        self.is_defunct = False
+        self.unknown_oid = None
+        self.dn = None
+        self.ldif = ""
+
+    def can_ignore_failure(self, error):
+        """Checks if we can safely ignore failure to apply an LDIF update"""
+        (num, errstr) = error.args
+
+        # Microsoft has marked objects as defunct that Samba doesn't know about
+        if num == ldb.ERR_NO_SUCH_OBJECT and self.is_defunct:
+            print("Defunct object %s doesn't exist, skipping" % self.dn)
+            return True
+        elif self.unknown_oid is not None:
+            print("Skipping unknown OID %s for object %s" % (self.unknown_oid, self.dn))
+            return True
+
+        return False
+
+    def apply(self, samdb):
+        """Applies a single LDIF update to the schema"""
+
+        try:
+            try:
+                samdb.modify_ldif(self.ldif, controls=['relax:0'])
+            except ldb.LdbError as e:
+                if e.args[0] == ldb.ERR_INVALID_ATTRIBUTE_SYNTAX:
+
+                    # REFRESH after a failed change
+
+                    # Otherwise the OID-to-attribute mapping in
+                    # _apply_updates_in_file() won't work, because it
+                    # can't lookup the new OID in the schema
+                    samdb.set_schema_update_now()
+
+                    samdb.modify_ldif(self.ldif, controls=['relax:0'])
+                else:
+                    raise
+        except ldb.LdbError as e:
+            if self.can_ignore_failure(e):
+                return 0
+            else:
+                print("Exception: %s" % e)
+                print("Encountered while trying to apply the following LDIF")
+                print("----------------------------------------------------")
+                print("%s" % self.ldif)
+
+                raise
+
+        return 1
+
+
+class cmd_domain_schema_upgrade(Command):
+    """Domain schema upgrading"""
+
+    synopsis = "%prog [options]"
+
+    takes_optiongroups = {
+        "sambaopts": options.SambaOptions,
+        "versionopts": options.VersionOptions,
+        "credopts": options.CredentialsOptions,
+    }
+
+    takes_options = [
+        Option("-H", "--URL", help="LDB URL for database or target server", type=str,
+               metavar="URL", dest="H"),
+        Option("-q", "--quiet", help="Be quiet", action="store_true"),  # unused
+        Option("-v", "--verbose", help="Be verbose", action="store_true"),
+        Option("--schema", type="choice", metavar="SCHEMA",
+               choices=["2012", "2012_R2", "2016", "2019"],
+               help="The schema file to upgrade to. Default is (Windows) 2019.",
+               default="2019"),
+        Option("--ldf-file", type=str, default=None,
+               help="Just apply the schema updates in the adprep/.LDF file(s) specified"),
+        Option("--base-dir", type=str, default=None,
+               help="Location of ldf files Default is ${SETUPDIR}/adprep.")
+    ]
+
+    def _apply_updates_in_file(self, samdb, ldif_file):
+        """
+        Applies a series of updates specified in an .LDIF file. The .LDIF file
+        is based on the adprep Schema updates provided by Microsoft.
+        """
+        count = 0
+        ldif_op = ldif_schema_update()
+
+        # parse the file line by line and work out each update operation to apply
+        for line in ldif_file:
+
+            line = line.rstrip()
+
+            # the operations in the .LDIF file are separated by blank lines. If
+            # we hit a blank line, try to apply the update we've parsed so far
+            if line == '':
+
+                # keep going if we haven't parsed anything yet
+                if ldif_op.ldif == '':
+                    continue
+
+                # Apply the individual change
+                count += ldif_op.apply(samdb)
+
+                # start storing the next operation from scratch again
+                ldif_op = ldif_schema_update()
+                continue
+
+            # replace the placeholder domain name in the .ldif file with the real domain
+            if line.upper().endswith('DC=X'):
+                line = line[:-len('DC=X')] + str(samdb.get_default_basedn())
+            elif line.upper().endswith('CN=X'):
+                line = line[:-len('CN=X')] + str(samdb.get_default_basedn())
+
+            values = line.split(':')
+
+            if values[0].lower() == 'dn':
+                ldif_op.dn = values[1].strip()
+
+            # replace the Windows-specific operation with the Samba one
+            if values[0].lower() == 'changetype':
+                line = line.lower().replace(': ntdsschemaadd',
+                                            ': add')
+                line = line.lower().replace(': ntdsschemamodify',
+                                            ': modify')
+                line = line.lower().replace(': ntdsschemamodrdn',
+                                            ': modrdn')
+                line = line.lower().replace(': ntdsschemadelete',
+                                            ': delete')
+
+            if values[0].lower() in ['rdnattid', 'subclassof',
+                                     'systemposssuperiors',
+                                     'systemmaycontain',
+                                     'systemauxiliaryclass']:
+                _, value = values
+
+                # The Microsoft updates contain some OIDs we don't recognize.
+                # Query the DB to see if we can work out the OID this update is
+                # referring to. If we find a match, then replace the OID with
+                # the ldapDisplayname
+                if '.' in value:
+                    res = samdb.search(base=samdb.get_schema_basedn(),
+                                       expression="(|(attributeId=%s)(governsId=%s))" %
+                                       (value, value),
+                                       attrs=['ldapDisplayName'])
+
+                    if len(res) != 1:
+                        ldif_op.unknown_oid = value
+                    else:
+                        display_name = str(res[0]['ldapDisplayName'][0])
+                        line = line.replace(value, ' ' + display_name)
+
+            # Microsoft has marked objects as defunct that Samba doesn't know about
+            if values[0].lower() == 'isdefunct' and values[1].strip().lower() == 'true':
+                ldif_op.is_defunct = True
+
+            # Samba has added the showInAdvancedViewOnly attribute to all objects,
+            # so rather than doing an add, we need to do a replace
+            if values[0].lower() == 'add' and values[1].strip().lower() == 'showinadvancedviewonly':
+                line = 'replace: showInAdvancedViewOnly'
+
+            # Add the line to the current LDIF operation (including the newline
+            # we stripped off at the start of the loop)
+            ldif_op.ldif += line + '\n'
+
+        return count
+
+    def _apply_update(self, samdb, update_file, base_dir):
+        """Wrapper function for parsing an LDIF file and applying the updates"""
+
+        print("Applying %s updates..." % update_file)
+
+        ldif_file = None
+        try:
+            ldif_file = open(os.path.join(base_dir, update_file))
+
+            count = self._apply_updates_in_file(samdb, ldif_file)
+
+        finally:
+            if ldif_file:
+                ldif_file.close()
+
+        print("%u changes applied" % count)
+
+        return count
+
+    def run(self, **kwargs):
+        try:
+            from samba.ms_schema_markdown import read_ms_markdown
+        except ImportError as e:
+            self.outf.write("Exception in importing markdown: %s" % e)
+            raise CommandError('Failed to import module markdown')
+        from samba.schema import Schema
+
+        updates_allowed_overridden = False
+        sambaopts = kwargs.get("sambaopts")
+        credopts = kwargs.get("credopts")
+        lp = sambaopts.get_loadparm()
+        creds = credopts.get_credentials(lp)
+        H = kwargs.get("H")
+        target_schema = kwargs.get("schema")
+        ldf_files = kwargs.get("ldf_file")
+        base_dir = kwargs.get("base_dir")
+
+        temp_folder = None
+
+        samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
+
+        # we're not going to get far if the config doesn't allow schema updates
+        if lp.get("dsdb:schema update allowed") is None:
+            lp.set("dsdb:schema update allowed", "yes")
+            print("Temporarily overriding 'dsdb:schema update allowed' setting")
+            updates_allowed_overridden = True
+
+        own_dn = ldb.Dn(samdb, samdb.get_dsServiceName())
+        master = get_fsmo_roleowner(samdb, str(samdb.get_schema_basedn()),
+                                    'schema')
+        if own_dn != master:
+            raise CommandError("This server is not the schema master.")
+
+        # if specific LDIF files were specified, just apply them
+        if ldf_files:
+            schema_updates = ldf_files.split(",")
+        else:
+            schema_updates = []
+
+            # work out the version of the target schema we're upgrading to
+            end = Schema.get_version(target_schema)
+
+            # work out the version of the schema we're currently using
+            res = samdb.search(base=samdb.get_schema_basedn(),
+                               scope=ldb.SCOPE_BASE, attrs=['objectVersion'])
+
+            if len(res) != 1:
+                raise CommandError('Could not determine current schema version')
+            start = int(res[0]['objectVersion'][0]) + 1
+
+            diff_dir = setup_path("adprep/WindowsServerDocs")
+            if base_dir is None:
+                # Read from the Schema-Updates.md file
+                temp_folder = tempfile.mkdtemp()
+
+                update_file = setup_path("adprep/WindowsServerDocs/Schema-Updates.md")
+
+                try:
+                    read_ms_markdown(update_file, temp_folder)
+                except Exception as e:
+                    print("Exception in markdown parsing: %s" % e)
+                    shutil.rmtree(temp_folder)
+                    raise CommandError('Failed to upgrade schema')
+
+                base_dir = temp_folder
+
+            for version in range(start, end + 1):
+                update = 'Sch%d.ldf' % version
+                schema_updates.append(update)
+
+                # Apply patches if we parsed the Schema-Updates.md file
+                diff = os.path.abspath(os.path.join(diff_dir, update + '.diff'))
+                if temp_folder and os.path.exists(diff):
+                    try:
+                        p = subprocess.Popen(['patch', update, '-i', diff],
+                                             stdout=subprocess.PIPE,
+                                             stderr=subprocess.PIPE, cwd=temp_folder)
+                    except (OSError, IOError):
+                        shutil.rmtree(temp_folder)
+                        raise CommandError("Failed to upgrade schema. "
+                                           "Is '/usr/bin/patch' missing?")
+
+                    stdout, stderr = p.communicate()
+
+                    if p.returncode:
+                        print("Exception in patch: %s\n%s" % (stdout, stderr))
+                        shutil.rmtree(temp_folder)
+                        raise CommandError('Failed to upgrade schema')
+
+                    print("Patched %s using %s" % (update, diff))
+
+        if base_dir is None:
+            base_dir = setup_path("adprep")
+
+        samdb.transaction_start()
+        count = 0
+        error_encountered = False
+
+        try:
+            # Apply the schema updates needed to move to the new schema version
+            for ldif_file in schema_updates:
+                count += self._apply_update(samdb, ldif_file, base_dir)
+
+            if count > 0:
+                samdb.transaction_commit()
+                print("Schema successfully updated")
+            else:
+                print("No changes applied to schema")
+                samdb.transaction_cancel()
+        except Exception as e:
+            print("Exception: %s" % e)
+            print("Error encountered, aborting schema upgrade")
+            samdb.transaction_cancel()
+            error_encountered = True
+
+        if updates_allowed_overridden:
+            lp.set("dsdb:schema update allowed", "no")
+
+        if temp_folder:
+            shutil.rmtree(temp_folder)
+
+        if error_encountered:
+            raise CommandError('Failed to upgrade schema')