Another alternative, if you don't want to split the code into separate batches, is to use EXEC
to create a nested scope/batch:
Print 'Beginning Upgrade'
Begin Transaction
-- --------------------------------------------------------------------
USE [MyDatabase];
/* Widgets now can be ordered and the order can be modified */
ALTER TABLE [dbo].[Widgets] ADD [IndexNumber] [int] NULL;
EXEC('DECLARE @ind INT
SET @ind = 0
UPDATE [dbo].[Widgets]
SET @ind = [IndexNumber] = @ind + 1;');
ALTER TABLE [dbo].[Widgets] ALTER COLUMN [IndexNumber] [int] NOT NULL;
-- --------------------------------------------------------------------
Commit tran
Print 'Upgrade completed'
The reason why the original code doesn't work because it tries to compile the entire batch before running it - the compilation fails and so it never even starts the transaction, let along alters the table.