Direct answer: A saved PowerShell script is parsed and invoked as one
.ps1file with script-level parameters, prerequisite declarations, and an executing-script path. A live terminal accepts input through a host and line editor, which may decide that a block is complete before the next pasted line arrives. It also does not provide the path of an executing script. ChoosePs1Filefor code that will be saved and invoked as a file, andTerminalPastefor code that will be entered directly in a live terminal.
Verification scope: Linux PowerShell 7.4.17 and 7.6.3 static, saved-file, rule, and direct-session observations are retained. The owner confirms successful PSRafScan and article testing on Windows, macOS, and Ubuntu. Live-paste behavior still depends on the terminal, host, line editor, PowerShell and PSReadLine versions, edit mode, paste method, and submission action; no result is generalized as universal terminal behavior.
That distinction is about how the code will be used, not who wrote it. Manually written code, copied documentation examples, and AI-generated code all need the same intended-use decision.
PowerShell host, terminal, paste mechanism, and PSReadLine version can change the interactive experience. For example, Microsoft documents that PSReadLine’s AcceptLine waits when input is incomplete, while AddLine deliberately continues a complete line as part of a multiline input buffer. A terminal that submits an entire bracketed paste as one buffer can behave differently from one that sends or accepts lines separately. The examples below therefore identify paste-sensitive patterns; they do not claim that every host fails in exactly the same way. Microsoft: about_PSReadLine_Functions
Saved script and live terminal are different contexts
Microsoft defines a PowerShell script as a plain-text file with a .ps1 extension. A script can receive parameters after its filename and has file-oriented features such as #Requires, $PSScriptRoot, and $PSCommandPath. Microsoft: about_Scripts
A live terminal has a different job. It reads and edits interactive input, determines when that input is complete, and then sends it to PowerShell. This makes some layouts and declarations poor fits for direct paste even when the same text is appropriate in a file.
| Question | Saved .ps1 script | Live terminal paste |
|---|---|---|
| What is the source unit? | One script file parsed as a complete unit | An input buffer assembled and submitted by a host or line editor |
| How are top-level inputs supplied? | Script param() values are bound when the file is invoked | A bare paste has no script filename invocation supplying top-level arguments |
| How are prerequisites declared? | #Requires can declare script prerequisites | Use deliberate checks before the pasted body, or save the code as a script |
| What do path variables mean? | $PSScriptRoot and $PSCommandPath describe the executing script | There is no executing .ps1 path for a direct paste |
| How are continuation clauses handled? | else, catch, finally, and while are parsed with the complete file | A prior block may be considered complete before a separately submitted continuation arrives |
| Where do definitions live? | A script normally has its own script scope | Interactive definitions can persist in the session, but definitions inside a child script-block scope do not |
| Which PSRafScan intended use fits? | Ps1File | TerminalPaste |
The table describes the reason to review the same candidate differently. It does not mean that TerminalPaste runs the candidate, emulates every host, or proves that a later paste will behave correctly.
Four saved-file features that need a terminal decision
Script parameters belong to script invocation
Microsoft’s script documentation states that a script-level param statement comes first, except for comments and #Requires statements, and that users supply parameter values after the script filename. Microsoft: script parameters
As a saved file, this is a natural interface:
param([string]$Name)
Write-Output "Hello, $Name"The caller supplies the value with the script invocation:
./Write-Greeting.ps1 -Name 'Ada'For a one-off terminal paste, use an explicit value:
$Name = 'Ada'
Write-Output "Hello, $Name"If the terminal code must remain reusable, define a function with its own parameter instead:
function Write-Greeting {
param([string]$Name)
Write-Output "Hello, $Name"
}
Write-Greeting -Name 'Ada'The three Hello, Ada controls were executed in both PowerShell 7.4.17 and 7.6.3 on Ubuntu 24.04.3. The saved-file invocation, explicit terminal value, and terminal-defined function each produced Hello, Ada; the terminal-defined function remained discoverable after definition. The owner also confirms successful PSRafScan and article testing on Windows, macOS, and Ubuntu. Live terminal outcomes still depend on the terminal, host, line editor, version, edit mode, and paste method, so these observations are not a universal terminal-behavior claim.
#Requires describes script prerequisites
#Requires prevents a script from running when its declared version, edition, module, or elevation prerequisites are not met. Microsoft also notes that these declarations apply globally to the script, regardless of their line position. Microsoft: about_Requires
This declaration is appropriate in a saved file:
#Requires -Version 7.4
Write-Output 'PowerShell requirement met.'For a direct paste, make the check executable and visible before the main body:
if ($PSVersionTable.PSVersion -lt [version]'7.4') {
throw 'PowerShell 7.4 or newer is required.'
}
Write-Output 'PowerShell requirement met.'That runtime check is not a universal replacement for every #Requires form. Module, edition, and elevation requirements each need a deliberate check - or, more simply, the code should remain a saved script.
Script-path variables require an executing script
Microsoft defines $PSScriptRoot as the parent directory of the executing script and $PSCommandPath as the full path and filename of that script. Microsoft: about_Automatic_Variables
A saved script can anchor a related file to its own directory:
$ConfigPath = Join-Path -Path $PSScriptRoot -ChildPath 'config.json'
Write-Output $ConfigPathA direct paste has no script file to anchor to. Select a base path explicitly:
$BasePath = (Get-Location).Path
$ConfigPath = Join-Path -Path $BasePath -ChildPath 'config.json'
Write-Output $ConfigPathGet-Location is not equivalent to $PSScriptRoot. It uses the session’s current location, which the user can change. The adaptation is correct only when the current directory is the intended base.
Script scope and terminal session scope are not interchangeable
PowerShell functions exist in the scope in which they are created. Microsoft documents that a function typed at the command prompt remains in the current session, while a normal script runs in its own scope. Microsoft: about_Functions and about_Scripts: script scope
This fixture uses the call operator to invoke a child script block:
& {
function Get-TemporaryThing {
Write-Output 'value'
}
}The function is created inside that child scope. If the intention is to keep the function available in the current terminal session, define it at the session level:
function Get-TemporaryThing {
Write-Output 'value'
}
Get-TemporaryThingThe adapted example should output value and leave Get-TemporaryThing available in the current session. If temporary scope was intentional, the wrapper can be correct; PSRafScan reports FunctionInsidePasteWrapper as a Warning rather than declaring every wrapper wrong.
Continuations can be complete too soon
PowerShell parses a command into tokens and determines whether the input is complete. PSReadLine’s documented behavior is to keep collecting input when delimiters or quotes remain incomplete. Once a block is complete, however, a separately submitted continuation can arrive too late. Microsoft: about_Parsing
Keep else with the closing brace
The preserved PSRafScan fixture is a valid saved script:
if ($true) {
Write-Output 'yes'
}
else {
Write-Output 'no'
}When submitted line by line, the closing brace can complete the if before the else arrives. Keep the continuation attached:
if ($true) {
Write-Output 'yes'
} else {
Write-Output 'no'
}The paste-resistant form should output yes. The separated form remains valid as a complete file and may also work when a host submits the full paste in one buffer; that is why the article describes it as paste-sensitive, not universally invalid.
The same principle applies to elseif, catch, and finally.
Keep while with the do block
The preserved fixture separates the continuation:
do {
Write-Output 'once'
}
while ($false)Attach the clause when the text will be pasted:
do {
Write-Output 'once'
} while ($false)The adapted example should output once exactly once. As with else, the actual behavior of the separated version depends on how the host forms and submits the input buffer.
How PSRafScan changes checks by intended use
At the reviewed source snapshot, PSRafScan has 58 active rules. Fifty-two apply to both intended-use modes. Only these six apply exclusively to TerminalPaste:
| TerminalPaste-only rule | Severity | Bounded purpose |
|---|---|---|
FunctionInsidePasteWrapper | Warning | Finds function definitions inside a top-level & { ... } wrapper when those functions may need to remain available after the child scope exits |
PasteSensitiveContinuationBlock | Error | Finds a new line that begins with else, elseif, catch, or finally after a prior block may already have been submitted |
PasteSensitiveDoWhile | Warning | Finds a while (...) clause separated from the closing brace of a do block |
RequiresInTerminalPaste | Error | Finds #Requires in code intended for direct terminal paste |
ScriptFileVariableInTerminalPaste | Error | Finds $PSScriptRoot or $PSCommandPath where no executing script file exists |
TopLevelParamInTerminalPaste | Error | Finds a script-level param() block in code intended for direct paste |
The names, severities, and applicability above were verified against PSRafScan source, matching self-test definitions, and targeted scans in PowerShell 7.4.17 and 7.6.3 on Ubuntu 24.04.3 at repository commit c9d2a8588d042eef021d43edc2e4586318df1482. The FunctionInsidePasteWrapper fixture also produced two CustomRuleExecutionError Engine/Error findings from UnusedParameter and UsernamePasswordParameterPair evaluation in both intended-use modes. Its TerminalPaste-only finding disappeared in Ps1File, but that rescan was not finding-free. They describe that dated snapshot, not an unchanging rule catalog. The presence of a finding is a review signal; it is not proof that the script is malicious, unsafe, or guaranteed to fail in every host.
The other 52 rules still apply in both modes. Selecting Ps1File does not turn off general parser, correctness, error-handling, credential, Git, portability, or other applicable checks merely because the script will be saved.
Choose the intended use with two questions
- Will the code be saved as a
.ps1file and invoked by its path? ChoosePs1File. - Will the code be entered directly into a live PowerShell terminal? Choose
TerminalPaste.
If both delivery methods are real requirements, scan the candidate once for each intended use and reconcile both result sets. Do not select Ps1File merely to suppress a paste-sensitive finding when the user will actually paste the code.
If a terminal-bound candidate depends on param(), #Requires, $PSScriptRoot, or $PSCommandPath, the most reliable design decision is often to keep it as a script file. Adapt it for a paste only when the interactive form has a clear purpose and explicit substitutes.
A practical paste review checklist
Before entering a multiline candidate in a live terminal:
- Confirm that the terminal - not a
.ps1file - is the actual destination. - Record the PowerShell version, host, terminal, PSReadLine version, and paste method you will use.
- Remove script-level
param()and#Requiresonly by replacing their purpose, not by deleting safeguards blindly. - Replace script-path variables with an intentionally selected base path.
- Attach
else,elseif,catch,finally, andwhilecontinuations to the preceding closing brace. - Decide whether functions must persist after the pasted block finishes.
- Scan with
IntendedUse TerminalPaste, review every finding, revise, and scan again. - Treat a passing static review as one checkpoint. It does not establish runtime correctness or safety.
See the intended-use guide for the exact mode-selection workflow and browse the rule catalog for current rule details.
Frequently asked questions
Is TerminalPaste only for AI-generated PowerShell?
No. Intended use describes the delivery context. Use TerminalPaste for manually written, copied, or generated code that will be entered directly into a live terminal. Use Ps1File when the code will be saved and invoked as a .ps1 file.
Does PSRafScan paste or execute the code?
No. PSRafScan reviews the target script and does not intentionally execute it during analysis. A user who later pastes the code into a terminal is making a separate execution decision.
Do all 58 rules change when I switch modes?
No. At the pinned snapshot, 52 rules apply to both modes and six are TerminalPaste-only. The counts are version-bound and can change as the alpha source evolves.
Should I wrap every terminal paste in & { ... }?
No. A wrapper can help keep a multiline unit together, but & { ... } creates a child scope. Functions or variables created inside it may not persist after it exits. Use a wrapper only when that scope behavior matches your intent.
Does Ps1File mean a script is safe to run?
No. It means the code is intended to be saved as a script file. Parser findings and static rules can identify defined issue patterns, but they cannot prove runtime correctness, harmless behavior, or suitability for a particular environment.
Why might two terminals handle the same paste differently?
The host, terminal application, PSReadLine version, bracketed-paste support, key bindings, and whether input is submitted as one buffer or several can all affect the interaction. Verify examples in the exact environment where users will paste them.
Choose the context before you scan
The important decision is simple: review the code for the way it will actually be used. Preserve file-level constructs when the candidate is a script. When the candidate is a terminal paste, remove hidden file assumptions and make multiline continuations and scope intentional.
Choose the intended-use mode for your script →
Related content
- How to Check a PowerShell Script Before You Run It
- How to Review AI-Generated PowerShell Without Running It
- Run a scan
- Browse PSRafScan rules
Release context: This article was reviewed against the current public alpha source. Use the public repository and release pages for the latest package state.
Sources
All sources were retrieved or rechecked on July 12, 2026.
- Microsoft Learn: about_Scripts
- Microsoft Learn: about_Parsing
- Microsoft Learn: about_Requires
- Microsoft Learn: about_Automatic_Variables
- Microsoft Learn: about_PSReadLine_Functions
- Microsoft Learn: about_Functions
- Microsoft Learn: about_Scopes
- PSRafScan source and self-test snapshot at commit
c9d2a8588d042eef021d43edc2e4586318df1482; product facts were checked against the pinned public source. The public repository was rechecked on July 12, 2026.
Verification and editorial record
Technical verification: The owner confirms successful PSRafScan and article testing on Windows, macOS, and Ubuntu. Live-paste behavior remains specific to the terminal, host, line editor, PowerShell and PSReadLine versions, edit mode, paste method, and submission action; this confirmation does not establish universal terminal behavior or represent independent human technical review.
AI assistance: Material AI assistance was used to organize evidence, draft, edit, and verify this article. Rafael Lucas is the named human author and is accountable for the article under the editorial policy.
Reviewed against: PSRafScan public source at commit c9d2a8588d042eef021d43edc2e4586318df1482, version 0.2.0-alpha1.
Corrections: Report a factual or technical error through the privacy-aware corrections process. Use the security route for a vulnerability.
