diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,10 @@
-## [_Unreleased_](https://github.com/freckle/stackctl/compare/v1.3.0.2...main)
+## [_Unreleased_](https://github.com/freckle/stackctl/compare/v1.4.0.0...main)
+
+## [v1.4.0.0](https://github.com/freckle/stackctl/compare/v1.3.0.2...v1.4.0.0)
+
+- Add `awsAssumeRole` for running an action as an assumed role
+- Refactor `Generate` interface to better support generating stacks with
+  pre-existing templates
 
 ## [v1.3.0.2](https://github.com/freckle/stackctl/compare/v1.3.0.1...v1.3.0.2)
 
diff --git a/src/Stackctl/AWS/Core.hs b/src/Stackctl/AWS/Core.hs
--- a/src/Stackctl/AWS/Core.hs
+++ b/src/Stackctl/AWS/Core.hs
@@ -6,6 +6,7 @@
   , awsSend
   , awsPaginate
   , awsAwait
+  , awsAssumeRole
 
   -- * Modifiers on 'AwsEnv'
   , awsWithin
@@ -25,6 +26,8 @@
 
 import Amazonka hiding (LogLevel(..))
 import qualified Amazonka as AWS
+import Amazonka.Auth.Keys (fromSession)
+import Amazonka.STS.AssumeRole
 import Conduit (ConduitM)
 import Control.Monad.Logger (defaultLoc, toLogStr)
 import Control.Monad.Trans.Resource (MonadResource)
@@ -103,6 +106,30 @@
 awsAwait w req = do
   AwsEnv env <- view awsEnvL
   await env w req
+
+awsAssumeRole
+  :: (MonadResource m, MonadReader env m, HasAwsEnv env)
+  => Text
+  -- ^ Role ARN
+  -> Text
+  -- ^ Session name
+  -> m a
+  -- ^ Action to run as the assumed role
+  -> m a
+awsAssumeRole role sessionName f = do
+  let req = newAssumeRole role sessionName
+
+  assumeEnv <- awsSimple "sts:AssumeRole" req $ \resp -> do
+    creds <- resp ^. assumeRoleResponse_credentials
+    token <- creds ^. authSessionToken
+
+    let
+      accessKeyId = creds ^. authAccessKeyId
+      secretAccessKey = creds ^. authSecretAccessKey
+
+    pure $ fromSession accessKeyId secretAccessKey token
+
+  local (awsEnvL . unL %~ assumeEnv) f
 
 awsWithin :: (MonadReader env m, HasAwsEnv env) => Region -> m a -> m a
 awsWithin r = local $ over (awsEnvL . unL) (within r)
diff --git a/src/Stackctl/Spec/Capture.hs b/src/Stackctl/Spec/Capture.hs
--- a/src/Stackctl/Spec/Capture.hs
+++ b/src/Stackctl/Spec/Capture.hs
@@ -10,7 +10,7 @@
 import Stackctl.AWS
 import Stackctl.AWS.Scope
 import Stackctl.Config (HasConfig)
-import Stackctl.DirectoryOption (HasDirectoryOption(..), unDirectoryOption)
+import Stackctl.DirectoryOption (HasDirectoryOption)
 import Stackctl.Spec.Generate
 import Stackctl.StackSpec
 import System.FilePath.Glob
@@ -74,26 +74,29 @@
   => CaptureOptions
   -> m ()
 runCapture CaptureOptions {..} = do
-  dir <- unDirectoryOption <$> view directoryOptionL
-
   let
     setScopeName scope =
       maybe scope (\name -> scope { awsAccountName = name }) scoAccountName
 
     generate' stack template path templatePath = do
+      let
+        stackName = StackName $ stack ^. stack_stackName
+        templateBody = templateBodyFromValue template
+
       void $ local (awsScopeL %~ setScopeName) $ generate Generate
-        { gOutputDirectory = dir
-        , gTemplatePath = templatePath
-        , gTemplateFormat = scoTemplateFormat
-        , gStackPath = path
-        , gStackName = StackName $ stack ^. stack_stackName
-        , gDescription = stackDescription stack
+        { gDescription = stackDescription stack
         , gDepends = scoDepends
         , gActions = Nothing
         , gParameters = parameters stack
         , gCapabilities = capabilities stack
         , gTags = tags stack
-        , gTemplateBody = templateBodyFromValue template
+        , gSpec = case path of
+          Nothing -> GenerateSpec stackName
+          Just sp -> GenerateSpecTo stackName sp
+        , gTemplate = case templatePath of
+          Nothing -> GenerateTemplate templateBody scoTemplateFormat
+          Just tp -> GenerateTemplateTo templateBody tp
+        , gOverwrite = False
         }
 
   results <- awsCloudFormationGetStackNamesMatching scoStackName
diff --git a/src/Stackctl/Spec/Generate.hs b/src/Stackctl/Spec/Generate.hs
--- a/src/Stackctl/Spec/Generate.hs
+++ b/src/Stackctl/Spec/Generate.hs
@@ -1,5 +1,7 @@
 module Stackctl.Spec.Generate
   ( Generate(..)
+  , GenerateSpec(..)
+  , GenerateTemplate(..)
   , generate
   , TemplateFormat(..)
   ) where
@@ -10,29 +12,38 @@
 import Stackctl.AWS
 import Stackctl.AWS.Scope
 import Stackctl.Config (HasConfig)
+import Stackctl.DirectoryOption
 import Stackctl.Spec.Discover (buildSpecPath)
 import Stackctl.StackSpec
 import Stackctl.StackSpecPath
 import Stackctl.StackSpecYaml
 
 data Generate = Generate
-  { gOutputDirectory :: FilePath
-  , gTemplatePath :: Maybe FilePath
-  -- ^ If not given, will use @{stack-name}.(yaml|json)@
-  , gTemplateFormat :: TemplateFormat
-  -- ^ Ignored if 'gTemplatePath' is given
-  , gStackPath :: Maybe FilePath
-  -- ^ If not given, will use @{stack-name}.yaml@
-  , gStackName :: StackName
-  , gDescription :: Maybe StackDescription
+  { gDescription :: Maybe StackDescription
   , gDepends :: Maybe [StackName]
   , gActions :: Maybe [Action]
   , gParameters :: Maybe [Parameter]
   , gCapabilities :: Maybe [Capability]
   , gTags :: Maybe [Tag]
-  , gTemplateBody :: TemplateBody
+  , gSpec :: GenerateSpec
+  , gTemplate :: GenerateTemplate
+  , gOverwrite :: Bool
   }
 
+data GenerateSpec
+  = GenerateSpec StackName
+  -- ^ Generate at an inferred name
+  | GenerateSpecTo StackName FilePath
+  -- ^ Generate to a given path
+
+data GenerateTemplate
+  = GenerateTemplate TemplateBody TemplateFormat
+  -- ^ Generate at an inferred name
+  | GenerateTemplateTo TemplateBody FilePath
+  -- ^ Generate to the given path
+  | UseExistingTemplate FilePath
+  -- ^ Assume template exists
+
 data TemplateFormat
   = TemplateFormatYaml
   | TemplateFormatJson
@@ -44,23 +55,26 @@
      , MonadReader env m
      , HasConfig env
      , HasAwsScope env
+     , HasDirectoryOption env
      )
   => Generate
   -> m FilePath
 generate Generate {..} = do
   let
-    defaultStackPath = unpack (unStackName gStackName) <.> "yaml"
-    defaultTemplatePath =
-      unpack (unStackName gStackName) <.> case gTemplateFormat of
-        TemplateFormatYaml -> "yaml"
-        TemplateFormatJson -> "json"
-
-    stackPath = fromMaybe defaultStackPath gStackPath
+    (stackName, stackPath) = case gSpec of
+      GenerateSpec name -> (name, unpack (unStackName name) <> ".yaml")
+      GenerateSpecTo name path -> (name, path)
 
-  specPath <- buildSpecPath gStackName stackPath
+    (mTemplateBody, templatePath) = case gTemplate of
+      GenerateTemplate body format ->
+        ( Just body
+        , case format of
+          TemplateFormatYaml -> unpack (unStackName stackName) <> ".yaml"
+          TemplateFormatJson -> unpack (unStackName stackName) <> ".json"
+        )
+      GenerateTemplateTo body path -> (Just body, path)
+      UseExistingTemplate path -> (Nothing, path)
 
-  let
-    templatePath = fromMaybe defaultTemplatePath gTemplatePath
     specYaml = StackSpecYaml
       { ssyDescription = gDescription
       , ssyTemplate = templatePath
@@ -71,9 +85,10 @@
       , ssyTags = tagsYaml . map TagYaml <$> gTags
       }
 
-  stackSpec <- buildStackSpec gOutputDirectory specPath specYaml
+  dir <- view $ directoryOptionL . to unDirectoryOption
+  specPath <- buildSpecPath stackName stackPath
+  stackSpec <- buildStackSpec dir specPath specYaml
 
   withThreadContext ["stackName" .= stackSpecStackName stackSpec] $ do
-    logInfo "Generating specification"
-    writeStackSpec stackSpec gTemplateBody
+    writeStackSpec gOverwrite stackSpec mTemplateBody
     pure $ stackSpecPathFilePath specPath
diff --git a/src/Stackctl/StackSpec.hs b/src/Stackctl/StackSpec.hs
--- a/src/Stackctl/StackSpec.hs
+++ b/src/Stackctl/StackSpec.hs
@@ -34,7 +34,7 @@
 import Stackctl.StackSpecYaml
 import qualified System.FilePath as FilePath
 import System.FilePath (takeExtension)
-import UnliftIO.Directory (createDirectoryIfMissing)
+import UnliftIO.Directory (createDirectoryIfMissing, doesFileExist)
 
 data StackSpec = StackSpec
   { ssSpecRoot :: FilePath
@@ -135,11 +135,29 @@
   dir = takeDirectory path
   ext = takeExtension path
 
-writeStackSpec :: MonadUnliftIO m => StackSpec -> TemplateBody -> m ()
-writeStackSpec stackSpec templateBody = do
-  writeTemplateBody templatePath templateBody
-  createDirectoryIfMissing True $ takeDirectory specPath
-  liftIO $ Yaml.encodeFile specPath $ stackSpecSpecBody stackSpec
+writeStackSpec
+  :: (MonadUnliftIO m, MonadLogger m)
+  => Bool
+  -> StackSpec
+  -> Maybe TemplateBody
+  -> m ()
+writeStackSpec overwrite stackSpec mTemplateBody = do
+  for_ mTemplateBody $ \templateBody -> do
+    logInfo $ "Writing template" :# ["path" .= templatePath]
+    writeTemplateBody templatePath templateBody
+
+  exists <- doesFileExist specPath
+
+  if exists && not overwrite
+    then do
+      let
+        reason :: Text
+        reason = "file exists and overwrite not set"
+      logInfo $ "Skipping" :# ["path" .= specPath, "reason" .= reason]
+    else do
+      logInfo $ "Writing specification" :# ["path" .= specPath]
+      createDirectoryIfMissing True $ takeDirectory specPath
+      liftIO $ Yaml.encodeFile specPath $ stackSpecSpecBody stackSpec
  where
   templatePath = unStackTemplate $ stackSpecTemplate stackSpec
   specPath =
diff --git a/stackctl.cabal b/stackctl.cabal
--- a/stackctl.cabal
+++ b/stackctl.cabal
@@ -1,6 +1,6 @@
 cabal-version:   1.18
 name:            stackctl
-version:         1.3.0.2
+version:         1.4.0.0
 license:         MIT
 license-file:    LICENSE
 copyright:       2022 Renaissance Learning Inc
