As you are using SQL 2012, you could (should?) be using the new THROW syntax, eg
BEGIN TRY
RAISERROR ( 'dummy', 16, 1 )
END TRY
BEGIN CATCH
DECLARE @msg NVARCHAR(2048) = ( SELECT 'Message with %% ' AS MSG );
THROW 51000, @msg, 1;
END CATCH
Although THROW doesn't like percentage signs (%) either so you still have to escape it, presumably because you can use FORMATMESSAGE with it. % signs on their own cause a headache for FORMATMESSAGE too as they're basically placeholders for parameters you pass in. So my honest advice would be to simply use the 'percent' word instead:
USE tempdb
EXEC sys.sp_addmessage
@msgnum = 60001
, @severity = 16
, @msgtext = N'I am 99.99999% this will NOT work.' -- OK this definitely won't work
, @lang = 'us_english'
, @replace = 'replace'
GO
DECLARE @msg NVARCHAR(2048) = FORMATMESSAGE(60001, 'test1' );
THROW 60001, @msg, 1;
GO
EXEC sys.sp_addmessage
@msgnum = 60001
, @severity = 16
, @msgtext = N'Even if you escape it, the sign %% disappears...' -- Even if you escape it
, @lang = 'us_english'
, @replace = 'replace'
GO
DECLARE @msg NVARCHAR(2048) = FORMATMESSAGE(60001, 'test1' );
THROW 60001, @msg, 1;
GO
EXEC sys.sp_addmessage
@msgnum = 60001
, @severity = 16
, @msgtext = N'I am 100 percent sure this will work.'
, @lang = 'us_english'
, @replace = 'replace'
GO
DECLARE @msg NVARCHAR(2048) = FORMATMESSAGE(60001, 'test1' );
THROW 60001, @msg, 1;
GO
-- Tidy up
EXEC sys.sp_dropmessage 60001