diff --git a/System/Directory.hs b/System/Directory.hs
--- a/System/Directory.hs
+++ b/System/Directory.hs
@@ -40,6 +40,9 @@
     , getUserDocumentsDirectory
     , getTemporaryDirectory
 
+    -- * PATH
+    , getExecSearchPath
+
     -- * Actions on files
     , removeFile
     , renameFile
@@ -1337,3 +1340,7 @@
 -}
 getTemporaryDirectory :: IO FilePath
 getTemporaryDirectory = D.getTemporaryDirectory >>= decodeFS
+
+-- | Get the contents of the @PATH@ environment variable.
+getExecSearchPath :: IO [FilePath]
+getExecSearchPath = D.getExecSearchPath >>= (`for` decodeFS)
diff --git a/System/Directory/Internal/Posix.hsc b/System/Directory/Internal/Posix.hsc
--- a/System/Directory/Internal/Posix.hsc
+++ b/System/Directory/Internal/Posix.hsc
@@ -148,6 +148,9 @@
     path <- getPath
     pure (findExecutablesInDirectoriesLazy path binary)
 
+getPath :: IO [OsPath]
+getPath = splitSearchPath <$> getEnvOs (os "PATH")
+
 exeExtensionInternal :: OsString
 exeExtensionInternal = exeExtension
 
@@ -390,10 +393,6 @@
           Nothing
           Nothing
     Just value -> pure value
-
--- | Get the contents of the @PATH@ environment variable.
-getPath :: IO [OsPath]
-getPath = splitSearchPath <$> getEnvOs (os "PATH")
 
 -- | $HOME is preferred, because the user has control over it. However, POSIX
 -- doesn't define it as a mandatory variable, so fall back to `getpwuid_r`.
diff --git a/System/Directory/Internal/Windows.hsc b/System/Directory/Internal/Windows.hsc
--- a/System/Directory/Internal/Windows.hsc
+++ b/System/Directory/Internal/Windows.hsc
@@ -4,7 +4,7 @@
 #if defined(mingw32_HOST_OS)
 ##if defined(i386_HOST_ARCH)
 ## define WINAPI stdcall
-##elif defined(x86_64_HOST_ARCH)
+##elif defined(x86_64_HOST_ARCH) || defined(aarch64_HOST_ARCH)
 ## define WINAPI ccall
 ##else
 ## error unknown architecture
@@ -29,6 +29,7 @@
   , pack
   , pathSeparator
   , splitDirectories
+  , splitSearchPath
   , takeExtension
   , toChar
   , unpack
@@ -41,6 +42,7 @@
 import qualified System.Win32.WindowsString.Shell as Win32
 import qualified System.Win32.WindowsString.Time as Win32
 import qualified System.Win32.WindowsString.Types as Win32
+import qualified System.Win32.WindowsString.Console as Win32
 
 type RawHandle = OsPath
 
@@ -53,6 +55,26 @@
 closeRaw :: RawHandle -> IO ()
 closeRaw _ = pure ()
 
+lookupEnvOs :: OsString -> IO (Maybe OsString)
+lookupEnvOs (OsString name) = (OsString <$>) <$> Win32.getEnv name
+
+getEnvOs :: OsString -> IO OsString
+getEnvOs name = do
+  env <- lookupEnvOs name
+  case env of
+    Nothing ->
+      throwIO $
+        mkIOError
+          doesNotExistErrorType
+          ("env var " <> show name <> " not found")
+          Nothing
+          Nothing
+    Just value -> pure value
+
+-- | Get the contents of the @PATH@ environment variable.
+getPath :: IO [OsPath]
+getPath = splitSearchPath <$> getEnvOs (os "PATH")
+
 createDirectoryInternal :: OsPath -> IO ()
 createDirectoryInternal path =
   (`ioeSetOsPath` path) `modifyIOError` do
@@ -665,24 +687,6 @@
 setAccessPermissions :: OsPath -> Permissions -> IO ()
 setAccessPermissions path Permissions{writable = w} = do
   setFilePermissions path (setWriteMode w 0)
-
-lookupEnvOs :: OsString -> IO (Maybe OsString)
-lookupEnvOs (OsString name) = do
-  result <-
-    Win32.withTString name $ \ pName ->
-    peekTStringWith 256 $ \ pBuffer size ->
-    c_GetEnvironmentVariable pName pBuffer size
-  case result of
-    Left errCode | errCode == win32_eRROR_ENVVAR_NOT_FOUND -> pure Nothing
-                 | otherwise -> Win32.failWith "GetEnvironmentVariable" errCode
-    Right value -> pure (Just (OsString value))
-
-foreign import WINAPI unsafe "windows.h GetEnvironmentVariableW"
-  c_GetEnvironmentVariable
-    :: Win32.LPWSTR
-    -> Win32.LPWSTR
-    -> Win32.DWORD
-    -> IO Win32.DWORD
 
 getFolderPath :: Win32.CSIDL -> IO OsPath
 getFolderPath what = OsString <$> Win32.sHGetFolderPath nullPtr what nullPtr 0
diff --git a/System/Directory/OsPath.hs b/System/Directory/OsPath.hs
--- a/System/Directory/OsPath.hs
+++ b/System/Directory/OsPath.hs
@@ -42,6 +42,9 @@
     , getUserDocumentsDirectory
     , getTemporaryDirectory
 
+    -- * PATH
+    , getExecSearchPath
+
     -- * Actions on files
     , removeFile
     , renameFile
@@ -1653,3 +1656,7 @@
 -}
 getTemporaryDirectory :: IO OsPath
 getTemporaryDirectory = getTemporaryDirectoryInternal
+
+-- | Get the contents of the @PATH@ environment variable.
+getExecSearchPath :: IO [OsPath]
+getExecSearchPath = getPath
diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,6 +1,12 @@
 Changelog for the [`directory`][1] package
 ==========================================
 
+## 1.3.10.0 (Dec 2025)
+
+  * Add `getExecSearchPath` as replacement for
+    `System.FilePath.getSearchPath`.
+    ([#198](https://github.com/haskell/directory/pull/198))
+
 ## 1.3.9.0 (Oct 2024)
 
   * Rely on `file-io` for file I/O.
diff --git a/directory.cabal b/directory.cabal
--- a/directory.cabal
+++ b/directory.cabal
@@ -1,6 +1,6 @@
 cabal-version:  2.2
 name:           directory
-version:        1.3.9.0
+version:        1.3.10.0
 license:        BSD-3-Clause
 license-file:   LICENSE
 maintainer:     libraries@haskell.org
@@ -59,11 +59,11 @@
     include-dirs: .
 
     build-depends:
-        base     >= 4.13.0 && < 4.21,
+        base     >= 4.13.0 && < 4.23,
         file-io  >= 0.1.4 && < 0.2,
-        time     >= 1.8.0 && < 1.15,
+        time     >= 1.8.0 && < 1.16,
     if os(windows)
-        build-depends: Win32 >= 2.13.3 && < 2.15
+        build-depends: Win32 >= 2.14.1.0 && < 2.15
     else
         build-depends: unix >= 2.8.0 && < 2.9
 
diff --git a/tests/Util.hs b/tests/Util.hs
--- a/tests/Util.hs
+++ b/tests/Util.hs
@@ -144,26 +144,54 @@
   where cleanup dir' | keep      = return ()
                      | otherwise = removePathForcibly dir'
 
+diffAsc' :: (j -> k -> Ordering)
+         -> (u -> v -> Bool)
+         -> [(j, u)]
+         -> [(k, v)]
+         -> ([(j, u)], [(k, v)])
+diffAsc' cmp eq = go id id
+  where
+    go a b [] [] = (a [], b [])
+    go a b jus [] = go (a . (jus <>)) b [] []
+    go a b [] kvs = go a (b . (kvs <>)) [] []
+    go a b jus@((j, u) : jus') kvs@((k, v) : kvs') =
+      case cmp j k of
+        LT -> go (a . ((j, u) :)) b jus' kvs
+        GT -> go a (b . ((k, v) :)) jus kvs'
+        EQ | eq u v -> go a b jus' kvs'
+           | otherwise -> go (a . ((j, u) :)) (b . ((k, v) :)) jus' kvs'
+
+diffAsc :: (Ord k, Eq v) => [(k, v)] -> [(k, v)] -> ([(k, v)], [(k, v)])
+diffAsc = diffAsc' compare (==)
+
+-- Environment variables may be sensitive, so don't log their values.
+scrubEnv :: (String, String) -> (String, String)
+scrubEnv (k, v)
+  -- Allowlist for nonsensitive variables.
+  | k `elem` ["XDG_CONFIG_HOME"] = (k, v)
+  | otherwise = (k, "<" <> show (length v) <> " chars>")
+
 isolateEnvironment :: IO a -> IO a
 isolateEnvironment = bracket getEnvs setEnvs . const
   where
+    -- Duplicate environment variables will cause problems for this code.
+    -- https://github.com/haskell/cabal/issues/10718
     getEnvs = List.sort . filter (\(k, _) -> k /= "") <$> getEnvironment
     setEnvs target = do
       current <- getEnvs
-      updateEnvs current target
+      let (deletions, insertions) = diffAsc current target
+      updateEnvs deletions insertions
       new <- getEnvs
       when (target /= new) $ do
-        -- Environment variables may be sensitive, so don't log them.
-        throwIO (userError "isolateEnvironment.setEnvs failed")
-    updateEnvs kvs1@((k1, v1) : kvs1') kvs2@((k2, v2) : kvs2') =
-      case compare k1 k2 of
-        LT -> unsetEnv k1 *> updateEnvs kvs1' kvs2
-        EQ | v1 == v2 -> updateEnvs kvs1' kvs2'
-           | otherwise -> setEnv k1 v2 *> updateEnvs kvs1' kvs2'
-        GT -> setEnv k2 v2 *> updateEnvs kvs1 kvs2'
-    updateEnvs [] [] = pure ()
-    updateEnvs kvs1 [] = for_ kvs1 (unsetEnv . fst)
-    updateEnvs [] kvs2 = for_ kvs2 (uncurry setEnv)
+        let (missing, extraneous) = diffAsc target new
+        throwIO (userError ("isolateEnvironment.setEnvs failed:" <>
+                            " deletions=" <> show (scrubEnv <$> deletions) <>
+                            " insertions=" <> show (scrubEnv <$> insertions) <>
+                            " missing=" <> show (scrubEnv <$> missing) <>
+                            " extraneous=" <> show (scrubEnv <$> extraneous)))
+    updateEnvs deletions insertions = do
+      for_ deletions (unsetEnv . fst)
+      for_ insertions (uncurry setEnv)
 
 isolateWorkingDirectory :: Bool -> OsPath -> IO a -> IO a
 isolateWorkingDirectory keep dir action = do
