Doesn't matter how long you think/search, you always come up with the answer right after you ask the question.
I was able to solve this by deliberately mis-using try/catch, so I looped through the structure, did a try on creating an object out of a each item as if it were a component, and on error, copied it to my snapshot structure. I also had to store it in a different scope, in my case I used session, since if I let it go to the default variables, there would be a circular reference that cause a structure with an infinite number of children.
EDIT: THIS DOES NOT DO WHAT I THOUGHT IT DID, SEE BELOW
<cfset session.varSnapShot = StructNew()>
<cfset loopList = StructKeyList(variables)>
<cfloop from="1" to="#ListLen(loopList)#" index="i">
<cftry>
<cfobject name="x#i#" component="#variables[ListGetAt(loopList,i)]#">
<cfcatch>
<cfset session.varSnapShot[ListGetAt(loopList,i)]= variables[ListGetAt(loopList,i)]>
</cfcatch>
</cftry>
</cfloop>
EDIT: Since the above doesn't actually do a deep copy (thanks Leigh) I came up with this:
<cfloop from="1" to="#ListLen(loopList)#" index="i">
<cfset metaData = GetMetaData(variables[ListGetAt(loopList,i)])>
<cfif isStruct(metaData) AND isDefined("metaData.type") AND metaData.type EQ "component">
<cfelse>
<cfset session.varSnapShot[ListGetAt(loopList,i)]= duplicate(variables[ListGetAt(loopList,i)])>
</cfif>
</cfloop>
This does make a deep copy but will still be a problem if a component is below the first level of an object. I wanted to create a recursive method, but It's an hour and a half past quitting time on a Friday. I will instead kill brain cells at the pub and maybe update this with the recursive method on Monday if I don't forget.