Add New-TemporaryDirectory cmdlet (#27549)

Co-authored-by: Justin Chung <124807742+jshigetomi@users.noreply.github.com>
This commit is contained in:
Kirtikumar Anandrao RamchandaniandJustin Chung authored and GitHub committed 2026-08-17 13:35:04 -05:00
1 parent 174a75396c
commit f83ccddf80
5 files changed
+226 -2

No files matched your search

@@ -0,0 +1,113 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.IO;
using System.Management.Automation;
namespace Microsoft.PowerShell.Commands
{
/// <summary>
/// The implementation of the "New-TemporaryDirectory" cmdlet.
/// </summary>
[Cmdlet(VerbsCommon.New, "TemporaryDirectory", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.Low, HelpUri = "https://go.microsoft.com/fwlink/?LinkId=2097032")]
[OutputType(typeof(System.IO.DirectoryInfo))]
public class NewTemporaryDirectoryCommand : Cmdlet
{
private const string NewTemporaryDirectoryInvalidPrefix = "NewTemporaryDirectoryInvalidPrefix";
private const string NewTemporaryDirectoryWriteError = "NewTemporaryDirectoryWriteError";
/// <summary>
/// Gets or sets an optional prefix for the temporary directory name.
/// </summary>
[Parameter]
[ValidateNotNullOrEmpty]
public string Prefix { get; set; }
/// <summary>
/// Returns a temporary directory.
/// </summary>
protected override void EndProcessing()
{
string tempPath = Path.GetTempPath();
if (!string.IsNullOrEmpty(Prefix) && IsInvalidPrefix(Prefix))
{
ThrowTerminatingError(
CreateErrorRecord(
new System.ArgumentException("The prefix contains invalid characters.", nameof(Prefix)),
NewTemporaryDirectoryInvalidPrefix,
ErrorCategory.InvalidArgument,
Prefix));
return;
}
string targetDescription = string.IsNullOrEmpty(Prefix)
? $"temporary directory under '{tempPath}'"
: $"temporary directory under '{tempPath}' with prefix '{Prefix}'";
if (!ShouldProcess(targetDescription, "Create temporary directory"))
{
return;
}
try
{
DirectoryInfo createdDirectory = string.IsNullOrEmpty(Prefix)
? Directory.CreateTempSubdirectory()
: Directory.CreateTempSubdirectory(Prefix);
WriteObject(createdDirectory);
}
catch (IOException ioException)
{
ThrowTerminatingError(
CreateErrorRecord(
ioException,
NewTemporaryDirectoryWriteError,
ErrorCategory.WriteError,
targetDescription));
}
catch (System.UnauthorizedAccessException unauthorizedAccessException)
{
ThrowTerminatingError(
CreateErrorRecord(
unauthorizedAccessException,
NewTemporaryDirectoryWriteError,
ErrorCategory.PermissionDenied,
targetDescription));
}
}
private static bool IsInvalidPrefix(string prefix)
{
if (prefix.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0)
{
return true;
}
foreach (char prefixCharacter in prefix)
{
if (prefixCharacter is '/' or '\\' || char.IsControl(prefixCharacter))
{
return true;
}
}
return prefix.Equals(".", System.StringComparison.Ordinal)
|| prefix.Equals("..", System.StringComparison.Ordinal);
}
private static ErrorRecord CreateErrorRecord(
System.Exception exception,
string errorId,
ErrorCategory category,
object targetObject)
{
return new ErrorRecord(
exception,
errorId,
category,
targetObject);
}
}
}
@@ -21,7 +21,7 @@ CmdletsToExport = @(
'Get-PSBreakpoint', 'Remove-PSBreakpoint', 'Set-PSBreakpoint', 'Get-PSCallStack', 'Export-PSSession',
'Import-PSSession', 'Get-Random', 'Get-SecureRandom', 'Invoke-RestMethod', 'Debug-Runspace', 'Get-Runspace',
'Disable-RunspaceDebug', 'Enable-RunspaceDebug', 'Get-RunspaceDebug', 'Start-Sleep', 'Join-String',
'Out-String', 'Select-String', 'ConvertFrom-StringData', 'Format-Table', 'New-TemporaryFile', 'New-TimeSpan',
'Out-String', 'Select-String', 'ConvertFrom-StringData', 'Format-Table', 'New-TemporaryDirectory', 'New-TemporaryFile', 'New-TimeSpan',
'Get-TraceSource', 'Set-TraceSource', 'Add-Type', 'Get-TypeData', 'Remove-TypeData', 'Update-TypeData',
'Get-UICulture', 'Get-Unique', 'Get-Uptime', 'Clear-Variable', 'Get-Variable', 'New-Variable',
'Remove-Variable', 'Set-Variable', 'Get-Verb', 'Write-Verbose', 'Write-Warning', 'Invoke-WebRequest',
@@ -20,7 +20,7 @@ CmdletsToExport = @(
'Remove-PSBreakpoint', 'Set-PSBreakpoint', 'Get-PSCallStack', 'Export-PSSession', 'Import-PSSession', 'Get-Random', 'Get-SecureRandom'
'Invoke-RestMethod', 'Debug-Runspace', 'Get-Runspace', 'Disable-RunspaceDebug', 'Enable-RunspaceDebug',
'Get-RunspaceDebug', 'ConvertFrom-SddlString', 'Start-Sleep', 'Join-String', 'Out-String', 'Select-String',
'ConvertFrom-StringData', 'Format-Table', 'New-TemporaryFile', 'New-TimeSpan', 'Get-TraceSource', 'Set-TraceSource',
'ConvertFrom-StringData', 'Format-Table', 'New-TemporaryDirectory', 'New-TemporaryFile', 'New-TimeSpan', 'Get-TraceSource', 'Set-TraceSource',
'Add-Type', 'Get-TypeData', 'Remove-TypeData', 'Update-TypeData', 'Get-UICulture', 'Get-Unique', 'Get-Uptime',
'Clear-Variable', 'Get-Variable', 'New-Variable', 'Remove-Variable', 'Set-Variable', 'Get-Verb', 'Write-Verbose',
'Write-Warning', 'Invoke-WebRequest', 'Format-Wide', 'ConvertTo-Xml', 'Select-Xml', 'Get-Error', 'Update-List',
@@ -0,0 +1,110 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
Describe "New-TemporaryDirectory" -Tags "CI" {
BeforeEach {
$tempDirectory = $null
}
AfterEach {
if ($tempDirectory -and (Test-Path -LiteralPath $tempDirectory.FullName)) {
Remove-Item -LiteralPath $tempDirectory.FullName -Force -Recurse -ErrorAction SilentlyContinue
}
}
It "creates a new temporary directory" {
$tempDirectory = New-TemporaryDirectory
$tempDirectory | Should -Exist
$tempDirectory | Should -BeOfType System.IO.DirectoryInfo
$tempDirectory.FullName | Should -BeLikeExactly "$([System.IO.Path]::GetTempPath())*"
(Get-Item -LiteralPath $tempDirectory.FullName).PSIsContainer | Should -BeTrue
}
It "creates unique temporary directories" {
$tempDirectory = New-TemporaryDirectory
$secondTempDirectory = New-TemporaryDirectory
try {
$secondTempDirectory.FullName | Should -Not -BeExactly $tempDirectory.FullName
$secondTempDirectory | Should -Exist
$secondTempDirectory | Should -BeOfType System.IO.DirectoryInfo
}
finally {
if ($secondTempDirectory -and (Test-Path -LiteralPath $secondTempDirectory.FullName)) {
Remove-Item -LiteralPath $secondTempDirectory.FullName -Force -Recurse -ErrorAction SilentlyContinue
}
}
}
It "with WhatIf does not create a directory" {
New-TemporaryDirectory -WhatIf | Should -BeNullOrEmpty
}
It "with WhatIf and Prefix does not create a directory" {
New-TemporaryDirectory -Prefix "Test_" -WhatIf | Should -BeNullOrEmpty
}
It "with WhatIf and Prefix reports a descriptive target" {
$prefix = "Test_"
$rawTarget = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), $prefix)
$whatIfOutput = & "$PSHOME/pwsh" -NoProfile -CommandWithArgs 'param($prefix) New-TemporaryDirectory -Prefix $prefix -WhatIf' $prefix 2>&1 | Out-String
$whatIfOutput | Should -Not -Match ([regex]::Escape("`"$rawTarget`""))
$whatIfOutput | Should -Match ([regex]::Escape("with prefix '$prefix'"))
}
It "rejects special relative path Prefix '<Prefix>'" -TestCases @(
@{ Prefix = "." }
@{ Prefix = ".." }
) {
param($Prefix)
{ New-TemporaryDirectory -Prefix $Prefix -ErrorAction Stop } | Should -Throw -ErrorId "NewTemporaryDirectoryInvalidPrefix,Microsoft.PowerShell.Commands.NewTemporaryDirectoryCommand"
}
It "rejects separator-like Prefix '<Prefix>'" -TestCases @(
@{ Prefix = "path/name" }
@{ Prefix = "path\name" }
@{ Prefix = "../name" }
@{ Prefix = "..\name" }
) {
param($Prefix)
{ New-TemporaryDirectory -Prefix $Prefix -ErrorAction Stop } | Should -Throw -ErrorId "NewTemporaryDirectoryInvalidPrefix,Microsoft.PowerShell.Commands.NewTemporaryDirectoryCommand"
}
It "rejects Prefix with control character '<CharacterCode>'" -TestCases @(
@{ CharacterCode = 10 }
@{ CharacterCode = 13 }
@{ CharacterCode = 9 }
@{ CharacterCode = 27 }
) {
param($CharacterCode)
$Prefix = "bad$([char]$CharacterCode)name"
{ New-TemporaryDirectory -Prefix $Prefix -ErrorAction Stop } | Should -Throw -ErrorId "NewTemporaryDirectoryInvalidPrefix,Microsoft.PowerShell.Commands.NewTemporaryDirectoryCommand"
}
It "has an OutputType of System.IO.DirectoryInfo" {
(Get-Command New-TemporaryDirectory).OutputType.Name | Should -Contain "System.IO.DirectoryInfo"
}
It "creates a directory with custom prefix" {
$prefix = "MyApp"
$tempDirectory = New-TemporaryDirectory -Prefix $prefix
$tempDirectory | Should -Exist
$tempDirectory | Should -BeOfType System.IO.DirectoryInfo
$tempDirectory.Name | Should -BeLike "$prefix*"
}
It "rejects Prefix with invalid file name characters" {
$invalidChars = [System.IO.Path]::GetInvalidFileNameChars()
if ($invalidChars.Count -gt 0) {
$badPrefix = "bad$($invalidChars[0])prefix"
{ New-TemporaryDirectory -Prefix $badPrefix -ErrorAction Stop } | Should -Throw -ErrorId "NewTemporaryDirectoryInvalidPrefix,Microsoft.PowerShell.Commands.NewTemporaryDirectoryCommand"
}
}
}
@@ -401,6 +401,7 @@ Describe "Verify aliases and cmdlets" -Tags "CI" {
"Cmdlet", "New-PSSessionOption", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "None"
"Cmdlet", "New-PSTransportOption", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "None"
"Cmdlet", "New-Service", "", $($FullCLR -or $CoreWindows ), "", "", "Medium"
"Cmdlet", "New-TemporaryDirectory", "", $( $CoreWindows -or $CoreUnix), "", "", "Low"
"Cmdlet", "New-TemporaryFile", "", $( $CoreWindows -or $CoreUnix), "", "", "Low"
"Cmdlet", "New-TimeSpan", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "None"
"Cmdlet", "New-Variable", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "Low"