DDL statements being commited within transaction
SQL Server does not auto-commit an explicit transaction.

What might be happening in you case is that the transaction is aborted and rolled back, but your script continues, executing the remainder without a wrapping transaction. This is particularly likely if you have GO statements in your script.

To implement cross batch error handling you can do something like this:

BEGIN TRAN
 CREATE TABLE #OriginalTransaction(i INT);
 BEGIN TRY
   -- do stuff here
 END TRY
 BEGIN CATCH
   IF(@@TRANCOUNT&gt;0)ROLLBACK;
 END CATCH;

GO
 IF(OBJECT_ID('tempdb..#OriginalTransaction') IS NOT NULL)
 BEGIN
  BEGIN TRY
    -- do more stuff here
  END TRY
  BEGIN CATCH
    IF(@@TRANCOUNT&gt;0)ROLLBACK;
  END CATCH;
 END;

GO
 IF(OBJECT_ID('tempdb..#OriginalTransaction') IS NOT NULL)
 BEGIN
   COMMIT;
 END;

GO
 IF(@@TRANCOUNT&gt;0)ROLLBACK;
GO
 IF(OBJECT_ID('tempdb..#OriginalTransaction') IS NOT NULL)
 BEGIN
   DROP TABLE #OriginalTransaction;
 END;