Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,140 @@
using System;
using System.IO;
using System.Management.Automation;
using System.Management.Automation.Internal;
using System.Threading;

namespace Microsoft.PowerShell.Commands
{
/// <summary>
/// The implementation of the "New-TemporaryFile" cmdlet
/// The implementation of the "New-TemporaryFile" cmdlet that has an optional 'Extension' Parameter property.
/// If this cmdlet errors then it throws a non-terminating error.
/// </summary>
[Cmdlet(VerbsCommon.New, "TemporaryFile", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkId=526726")]
[OutputType(typeof(System.IO.FileInfo))]
public class NewTemporaryFileCommand : Cmdlet
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a comment section here that describes this public property.

{
private const string defaultExtension = ".tmp";

/// <summary>
/// returns a TemporaryFile
/// Specify a different file extension other than the default one, which is '.tmp'. The period in this parameter is optional.
/// </summary>
[Parameter()]
[ValidateNotNullOrEmpty]
public string Extension { get; set; } = defaultExtension;

/// <summary>
/// Validate Extension.
/// </summary>
protected override void BeginProcessing()
{
// Check for invalid characters in extension
if (Extension.IndexOfAny(Path.GetInvalidFileNameChars()) != -1)
{
WriteError(
new ErrorRecord(
new ArgumentException(
StringUtil.Format(UtilityCommonStrings.InvalidCharacterInParameter,
nameof(Extension), Extension)),
"NewTemporaryInvalidArgument",
ErrorCategory.InvalidArgument,
Extension));
return;
}
}

/// <summary>
/// Returns a TemporaryFile.
/// </summary>
protected override void EndProcessing()
{
string filePath = null;
string tempPath = System.Environment.GetEnvironmentVariable("TEMP");
string temporaryFilePath = null;
string tempPath = Path.GetTempPath();

if (ShouldProcess(tempPath))
{
try
{
filePath = Path.GetTempFileName();
}
catch (Exception e)
if (Extension.Equals(defaultExtension, StringComparison.Ordinal))
{
WriteError(
new ErrorRecord(
e,
"NewTemporaryFileWriteError",
ErrorCategory.WriteError,
tempPath));
return;
try
{
temporaryFilePath = Path.GetTempFileName();
}
catch (Exception e)
{
WriteError(
new ErrorRecord(
e,
"NewTemporaryFileWriteError",
ErrorCategory.WriteError,
tempPath));
return;
}
}
if (!string.IsNullOrEmpty(filePath))
else
{
FileInfo file = new FileInfo(filePath);
WriteObject(file);
int attempts = 0;
bool creationOfFileSuccessful = false;

// In case the random temporary file already exists, retry
while (attempts++ < 10 && !creationOfFileSuccessful)
{
try
{
temporaryFilePath = Path.GetTempFileName(); // this already creates the temporary file
// rename file
var temporaryFileWithCustomExtension = Path.ChangeExtension(temporaryFilePath, Extension);
File.Move(temporaryFilePath, temporaryFileWithCustomExtension);
temporaryFilePath = temporaryFileWithCustomExtension;
creationOfFileSuccessful = true;
WriteDebug($"Created temporary file {temporaryFilePath}.");
}
catch (IOException) // file already exists -> retry
{
attempts++;
if (temporaryFilePath != null)
{
try
{
File.Delete(temporaryFilePath);
}
catch (Exception exception)
{
WriteError(
new ErrorRecord(
exception,
"NewTemporaryFileWriteError",
ErrorCategory.WriteError,
tempPath));
Thread.Sleep(10); // to increase chance of success in the next try
}
}
}
catch (Exception exception) // fatal error, which could be e.g. insufficient permissions, etc.
{
WriteError(
new ErrorRecord(
exception,
"NewTemporaryFileWriteError",
ErrorCategory.WriteError,
tempPath));
return;
}
}

if (!creationOfFileSuccessful)
{
WriteError(
new ErrorRecord(
new IOException(StringUtil.Format(UtilityCommonStrings.CouldNotCreateTemporaryFilename, tempPath)),
"NewTemporaryFileResourceUnavailable",
ErrorCategory.ResourceUnavailable,
tempPath));
return;
}
}

var temporaryFileInfo = new FileInfo(temporaryFilePath);
WriteObject(temporaryFileInfo);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,4 +177,10 @@
<data name="NoZoneIdentifierFileStream" xml:space="preserve">
<value>The file is not blocked: {0}</value>
</data>
<data name="InvalidCharacterInParameter" xml:space="preserve">
<value>Parameter '{0}' contains at least one invalid character. Its value is: '{1}'</value>
</data>
<data name="CouldNotCreateTemporaryFilename" xml:space="preserve">
<value>'Could not create a temporary file name in {0}.</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,77 @@
<#
Purpose:
Verify that New-TemporaryFile creates a temporary file.
It has an 'Extension' parameter to change the default extension '.tmp'.

Action:
Run New-TemporaryFile.

Expected Result:
A FileInfo object for the temporary file is returned.
A FileInfo object for the temporary file is returned and the temporary file gets created correctly.
#>

Describe "NewTemporaryFile" -Tags "CI" {

BeforeAll {
$defaultExtension = '.tmp'
}

AfterEach {
if ($null -ne $script:tempFile)
{
# tempFile variable needs script scope because it gets defined in It block
Remove-Item $script:tempFile -ErrorAction SilentlyContinue -Force
}
}

It "creates a new temporary file" {
$tempFile = New-TemporaryFile
$script:tempFile = New-TemporaryFile
$tempFile | Should Exist
$tempFile | Should BeOfType System.IO.FileInfo
$tempFile.Extension | Should be $defaultExtension
}

Test-Path $tempFile | Should be $true
It "creates a new temporary file with the Extension parameter being the default" {
$script:tempFile = New-TemporaryFile -Extension $defaultExtension
$tempFile | Should Exist
$tempFile.Extension | Should be $defaultExtension
}

It "creates a new temporary file with the Extension parameter being the default but different casing" {
$defaultExtensionWithUpperCasing = $defaultExtension.ToUpper()
$script:tempFile = New-TemporaryFile -Extension $defaultExtensionWithUpperCasing
$tempFile | Should Exist
# On Linux '.TMP' and '.tmp' would not be the same, therefore we need to check that the initial 'tmp' file got removed.
if ($IsLinux)
{
[System.IO.Path]::ChangeExtension($tempFile, $defaultExtension) | Should Not Exist
}
$tempFile.Extension | Should be $defaultExtensionWithUpperCasing
}

It "creates a new temporary file with a specific extension" -TestCases @(
@{ extension = 'csv' }
@{ extension = '.csv' }
@{ extension = '..csv' }
@{ extension = 'txt.csv' }
@{ extension = '.txt.csv' }
) -Test {
Param ([string]$extension)

$script:tempFile = New-TemporaryFile -Extension $extension
$tempFile | Should Exist
# Because the internal algorithm does renaming it is worthwhile checking that the original file does not get left behind
[System.IO.Path]::ChangeExtension($tempFile, $defaultExtension) | Should Not Exist
$tempFile | Should BeOfType System.IO.FileInfo
$tempFile.FullName.EndsWith($extension) | Should be $true
$tempFile.Extension | Should be ".csv"
}

if(Test-Path $tempFile)
It "New-TemporaryItem with an an invalid character in the -Extension parameter should throw NewTemporaryInvalidArgument error" {
$invalidFileNameChars = [System.IO.Path]::GetInvalidFileNameChars()
foreach($invalidFileNameChar in $invalidFileNameChars)
{
Remove-Item $tempFile -ErrorAction SilentlyContinue -Force
{ New-TemporaryFile -Extension $invalidFileNameChar -ErrorAction Stop } | ShouldBeErrorId "NewTemporaryInvalidArgument,Microsoft.PowerShell.Commands.NewTemporaryFileCommand"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't make sense to test for all invalid chars - enough of one - we should check only the error id.

}
}

Expand Down