Timeout expired obtaining a connection from the pool in PowerShell System.Data.SqlClient scripts
0 reputation · 01 Feb 2026, 08:22 UTC
0 reputation · 01 Feb 2026, 08:22 UTC
In Windows PowerShell 5.1 and PowerShell 7+ sessions using System.Data.SqlClient, scripts that create SqlConnection objects without explicit disposal can intermittently throw a SqlException with the message: "Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached." The underlying .NET connection pool does not automatically reclaim connections when PowerShell variables leave scope; they remain pooled until explicitly closed or the process ends.
An unresolved design decision in PowerShell is whether it should automatically call Dispose on .NET objects that implement IDisposable when they go out of scope, which would prevent such leaks but is not currently implemented. Because PowerShell sessions are often long-running, this leaves module authors without language-level guarantees for deterministic cleanup.
When building reusable functions that open SqlConnection objects, what is the most reliable pattern for ensuring connections return to the pool without relying on garbage collection? Should authors explicitly invoke Close or Dispose in every code path, or is there a documented alternative that addresses the unresolved automatic-disposal question? How can long-running scripts avoid intermittent pool exhaustion when the language does not enforce scope-based disposal?
To prevent "Timeout expired" errors in PowerShell, you must implement a try...finally block to explicitly call .Dispose() or .Close() on every SqlConnection object. Because PowerShell does not automatically call Dispose() when a variable goes out of scope, connections remain active in the .NET pool until the Garbage Collector (GC) eventually runs or the session terminates.
The most robust pattern for reusable functions is to instantiate the connection and immediately wrap its usage in a try block, ensuring the finally block handles the cleanup regardless of whether the script succeeded or threw an exception.
$connection = New-Object System.Data.SqlClient.SqlConnection($connectionString) try { $connection.Open() # Execute your SQL commands here } finally { if ($null -ne $connection) { $connection.Dispose() } }Technical Explanation and Assumptions
This approach assumes the use of
System.Data.SqlClient(orMicrosoft.Data.SqlClient) in PowerShell 5.1 or 7+. In these environments,SqlConnectionimplementsIDisposable. While.Close()and.Dispose()are functionally equivalent for returning a connection to the pool,.Dispose()is the standard .NET practice for resource cleanup.Why scope-based disposal fails: Unlike C#
usingstatements, PowerShell does not provide a native language construct that guarantees the disposal of .NET objects at the end of a block. If a script opens connections in a loop or a frequently called function without explicit disposal, the pool will reach itsMax Pool Size(default 100) long before the GC triggers a collection.Verification and Diagnostics
To verify that your cleanup pattern is working, you can monitor active connections on the SQL Server instance using Dynamic Management Views (DMVs):
SELECT count(*) as ConnectionCount FROM sys.dm_exec_connections WHERE client_net_address = 'Your_Client_IP';If the count increases steadily during script execution and does not drop immediately after the function completes, a leak is still present.
Recommendation Summary
try...finally to call .Dispose().Max Pool Size in the connection string; this only delays the crash.Diagnostic Question: Are you using a shared global connection object across multiple functions, or is a new connection instantiated within each function call?
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.