This is what I came up with, but even in this small example, it takes a long time to execute, so I must be doing something silly...
The reason that it's so slow is because you are generating a very large tally table (~3 million rows) on the fly. You only need a tally table with numbers up to 50 (the length of the value column).
Use TOP 50 to limit the size of the tally table. No need for a CROSS JOIN also.
...
WITH cteTally
AS ( SELECT TOP 50 row_number() OVER ( ORDER BY o.ID ) AS N
FROM master.sys.sysobjects o
),
...
Performance goes from 13sec to 0sec on my machine.