diff --git a/Rules/MissingTryBlock.cs b/Rules/MissingTryBlock.cs
new file mode 100644
index 000000000..0ef2a0d23
--- /dev/null
+++ b/Rules/MissingTryBlock.cs
@@ -0,0 +1,83 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Management.Automation.Language;
+
+#if !CORECLR
+using System.ComponentModel.Composition;
+#endif
+
+namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules
+{
+#if !CORECLR
+ [Export(typeof(IScriptRule))]
+#endif
+
+ ///
+ /// Rule that warns when reserved words are used as function names
+ ///
+ public class MissingTryBlock : IScriptRule
+ {
+ ///
+ /// Analyzes the PowerShell AST for uses of reserved words as function names.
+ ///
+ /// The PowerShell Abstract Syntax Tree to analyze.
+ /// The name of the file being analyzed (for diagnostic reporting).
+ /// A collection of diagnostic records for each violation.
+ public IEnumerable AnalyzeScript(Ast ast, string fileName)
+ {
+ if (ast == null) throw new ArgumentNullException(Strings.NullAstErrorMessage);
+
+ // Find all FunctionDefinitionAst in the Ast
+ var missingTryAsts = ast.FindAll(testAst =>
+ // Normally should be part of a TryStatementAst
+ testAst is StringConstantExpressionAst stringAst &&
+ // Catch of finally are reserved keywords and should be bare words
+ stringAst.StringConstantType == StringConstantType.BareWord &&
+ (
+ String.Equals(stringAst.Value, "catch", StringComparison.OrdinalIgnoreCase) ||
+ String.Equals(stringAst.Value, "finally", StringComparison.OrdinalIgnoreCase)
+ ) &&
+ stringAst.Parent is CommandAst commandAst &&
+ // Only violate if the catch or finally is the first command element
+ commandAst.CommandElements[0] == stringAst,
+ true
+ );
+
+ foreach (StringConstantExpressionAst missingTryAst in missingTryAsts)
+ {
+ yield return new DiagnosticRecord(
+ string.Format(
+ CultureInfo.CurrentCulture,
+ Strings.MissingTryBlockError,
+ CultureInfo.CurrentCulture.TextInfo.ToTitleCase(missingTryAst.Value)),
+ missingTryAst.Extent,
+ GetName(),
+ DiagnosticSeverity.Error,
+ fileName,
+ missingTryAst.Value
+ );
+ }
+ }
+
+ public string GetCommonName() => Strings.MissingTryBlockCommonName;
+
+ public string GetDescription() => Strings.MissingTryBlockDescription;
+
+ public string GetName() => string.Format(
+ CultureInfo.CurrentCulture,
+ Strings.NameSpaceFormat,
+ GetSourceName(),
+ Strings.MissingTryBlockName);
+
+ public RuleSeverity GetSeverity() => RuleSeverity.Warning;
+
+ public string GetSourceName() => Strings.SourceName;
+
+ public SourceType GetSourceType() => SourceType.Builtin;
+ }
+}
\ No newline at end of file
diff --git a/Rules/Strings.resx b/Rules/Strings.resx
index 2a04fd759..1b868ea90 100644
--- a/Rules/Strings.resx
+++ b/Rules/Strings.resx
@@ -276,6 +276,18 @@
Module Manifest Fields
+
+ MissingTryBlock
+
+
+ Missing try block
+
+
+ The catch and finally blocks should be preceded by a try block.
+
+
+ {0} is missing a try block
+
If a script file is in a PowerShell module folder, then that folder must be loadable.
diff --git a/Tests/Rules/MissingTryBlock.tests.ps1 b/Tests/Rules/MissingTryBlock.tests.ps1
new file mode 100644
index 000000000..bb28ced9b
--- /dev/null
+++ b/Tests/Rules/MissingTryBlock.tests.ps1
@@ -0,0 +1,133 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+
+[Diagnostics.CodeAnalysis.SuppressMessage('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'False positive')]
+param()
+
+BeforeAll {
+ $ruleName = "PSMissingTryBlock"
+}
+
+Describe "MissingTryBlock" {
+ Context "Violates" {
+ It "Catch is missing a try block" {
+ $scriptDefinition = { catch { "An error occurred." } }.ToString()
+ $violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule @($ruleName)
+ $violations.Count | Should -Be 1
+ $violations.Severity | Should -Be Error
+ $violations.Extent.Text | Should -Be catch
+ $violations.Message | Should -Be 'Catch is missing a try block'
+ $violations.RuleSuppressionID | Should -Be catch
+ }
+
+ It "Finally is missing a try block" {
+ $scriptDefinition = { finally { "Finalizing..." } }.ToString()
+ $violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule @($ruleName)
+ $violations.Count | Should -Be 1
+ $violations.Severity | Should -Be Error
+ $violations.Extent.Text | Should -Be finally
+ $violations.Message | Should -Be 'Finally is missing a try block'
+ $violations.RuleSuppressionID | Should -Be finally
+ }
+
+ It "Single line catch and finally is missing a try block" {
+ $scriptDefinition = {
+ catch { "An error occurred." } finally { "Finalizing..." }
+ }.ToString()
+ $violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule @($ruleName)
+ $violations.Count | Should -Be 1
+ $violations.Severity | Should -Be Error
+ $violations.Extent.Text | Should -Be catch
+ $violations.Message | Should -Be 'Catch is missing a try block'
+ $violations.RuleSuppressionID | Should -Be catch
+ }
+
+ It "Multi line catch and finally is missing a try block" {
+ $scriptDefinition = {
+ catch { "An error occurred." }
+ finally { "Finalizing..." }
+ }.ToString()
+ $violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule @($ruleName)
+ $violations.Count | Should -Be 2
+ $violations[0].Severity | Should -Be Error
+ $violations[0].Extent.Text | Should -Be catch
+ $violations[0].Message | Should -Be 'Catch is missing a try block'
+ $violations[0].RuleSuppressionID | Should -Be catch
+ $violations[1].Severity | Should -Be Error
+ $violations[1].Extent.Text | Should -Be finally
+ $violations[1].Message | Should -Be 'Finally is missing a try block'
+ $violations[1].RuleSuppressionID | Should -Be finally
+ }
+ }
+
+ Context "Compliant" {
+ It "try-catch block" {
+ $scriptDefinition = {
+ try { NonsenseString }
+ catch { "An error occurred." }
+ }.ToString()
+ $violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule @($ruleName)
+ $violations | Should -BeNullOrEmpty
+ }
+
+ It "try-catch-final statement" {
+ $scriptDefinition = {
+ try { NonsenseString }
+ catch { "An error occurred." }
+ finally { "Finalizing..." }
+ }.ToString()
+ $violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule @($ruleName)
+ $violations | Should -BeNullOrEmpty
+ }
+
+ It "Single line try statement" {
+ $scriptDefinition = {
+ try { NonsenseString } catch { "An error occurred." } finally { "Finalizing..." }
+ }.ToString()
+ $violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule @($ruleName)
+ $violations | Should -BeNullOrEmpty
+ }
+
+ It "Catch as parameter" {
+ $scriptDefinition = { Write-Host Catch }.ToString()
+ $violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule @($ruleName)
+ $violations | Should -BeNullOrEmpty
+ }
+
+ It "Catch as double quoted string" {
+ $scriptDefinition = { "Catch" }.ToString()
+ $violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule @($ruleName)
+ $violations | Should -BeNullOrEmpty
+ }
+
+ It "Catch as single quoted string" {
+ $scriptDefinition = { 'Catch' }.ToString()
+ $violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule @($ruleName)
+ $violations | Should -BeNullOrEmpty
+ }
+ }
+
+ Context "Suppressed" {
+ It "Multi line catch and finally is missing a try block" {
+ $scriptDefinition = {
+ [Diagnostics.CodeAnalysis.SuppressMessage('PSMissingTryBlock', '', Justification = 'Test')]
+ param()
+ catch { "An error occurred." }
+ finally { "Finalizing..." }
+ }.ToString()
+ $violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule @($ruleName)
+ $violations | Should -BeNullOrEmpty
+ }
+
+ It "Multi line catch and finally is missing a try block for catch only" {
+ $scriptDefinition = {
+ [Diagnostics.CodeAnalysis.SuppressMessage('PSMissingTryBlock', 'finally', Justification = 'Test')]
+ param()
+ catch { "An error occurred." }
+ finally { "Finalizing..." }
+ }.ToString()
+ $violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule @($ruleName)
+ $violations.Count | Should -Be 1
+ }
+ }
+}
\ No newline at end of file
diff --git a/docs/Rules/MissingTryBlock.md b/docs/Rules/MissingTryBlock.md
new file mode 100644
index 000000000..eb18a5f65
--- /dev/null
+++ b/docs/Rules/MissingTryBlock.md
@@ -0,0 +1,34 @@
+---
+description: Missing Try Block
+ms.date: 04/22/2026
+ms.topic: reference
+title: MissingTryBlock
+---
+# MissingTryBlock
+
+**Severity Level: Error**
+
+## Description
+
+The `catch` and `finally` blocks should be preceded by a `try` block.
+Otherwise, the `catch` and `finally` blocks will be interpreted as commands, which is likely a mistake and result
+in a "*The term 'catch' is not recognized as a name of a cmdlet*" error at runtime.
+
+## How
+
+Add a `try` block before the `catch` and `finally` blocks.
+
+## Example
+
+### Wrong
+
+```powershell
+catch { "An error occurred." }
+```
+
+### Correct
+
+```powershell
+try { $a = 1 / $b }
+catch { "Attempted to divide by zero." }
+```
diff --git a/docs/Rules/README.md b/docs/Rules/README.md
index fca031e33..846eb0799 100644
--- a/docs/Rules/README.md
+++ b/docs/Rules/README.md
@@ -50,6 +50,7 @@ The PSScriptAnalyzer contains the following rule definitions.
| [DSCUseVerboseMessageInDSCResource](./DSCUseVerboseMessageInDSCResource.md) | Error | Yes | |
| [MisleadingBacktick](./MisleadingBacktick.md) | Warning | Yes | |
| [MissingModuleManifestField](./MissingModuleManifestField.md) | Warning | Yes | |
+| [MissingTryBlock](./MissingTryBlock.md) | Error | Yes | |
| [PlaceCloseBrace](./PlaceCloseBrace.md) | Warning | No | Yes |
| [PlaceOpenBrace](./PlaceOpenBrace.md) | Warning | No | Yes |
| [PossibleIncorrectComparisonWithNull](./PossibleIncorrectComparisonWithNull.md) | Warning | Yes | |