From 520ec03af4cf1ebf579d99e3ca4bed0b482f5927 Mon Sep 17 00:00:00 2001 From: yotsuda Date: Sat, 15 Nov 2025 15:55:51 +0900 Subject: [PATCH 1/4] Fix Invoke-RestMethod to support read-only files in multipart form data Fixes #25482 Changed FileStream to use FileAccess.Read when uploading files via -Form parameter, allowing read-only files to be uploaded successfully. --- .../Common/WebRequestPSCmdlet.Common.cs | 2 +- .../WebCmdlets.Tests.ps1 | 55 +++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs index 7a784a1e495..bf955f824cb 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs @@ -1995,7 +1995,7 @@ private static StreamContent GetMultipartStreamContent(object fieldName, Stream /// The file to use for the private static StreamContent GetMultipartFileContent(object fieldName, FileInfo file) { - StreamContent result = GetMultipartStreamContent(fieldName: fieldName, stream: new FileStream(file.FullName, FileMode.Open)); + StreamContent result = GetMultipartStreamContent(fieldName: fieldName, stream: new FileStream(file.FullName, FileMode.Open, FileAccess.Read)); result.Headers.ContentDisposition.FileName = file.Name; result.Headers.ContentDisposition.FileNameStar = file.Name; diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 index 1ddb9f10ba9..861d0677927 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 @@ -1727,6 +1727,34 @@ Describe "Invoke-WebRequest tests" -Tags "Feature", "RequireAdminOnWindows" { $result.Files[0].Content | Should -Match $file1Contents } + It "Verifies Invoke-WebRequest -Form supports read-only file values" { + # Create a read-only test file + $readOnlyFile = Join-Path $TestDrive "readonly-test.txt" + "ReadOnly test content" | Out-File -FilePath $readOnlyFile -Encoding utf8 + Set-ItemProperty -Path $readOnlyFile -Name IsReadOnly -Value $true + + try { + $form = @{TestFile = [System.IO.FileInfo]$readOnlyFile} + $uri = Get-WebListenerUrl -Test 'Multipart' + $response = Invoke-WebRequest -Uri $uri -Form $form -Method 'POST' + $result = $response.Content | ConvertFrom-Json + + $result.Headers.'Content-Type' | Should -Match 'multipart/form-data' + $result.Files.Count | Should -Be 1 + + $result.Files[0].Name | Should -BeExactly "TestFile" + $result.Files[0].FileName | Should -BeExactly "readonly-test.txt" + $result.Files[0].ContentType | Should -BeExactly 'application/octet-stream' + $result.Files[0].Content | Should -Match "ReadOnly test content" + } finally { + # Clean up: remove read-only attribute before cleanup + if (Test-Path $readOnlyFile) { + Set-ItemProperty -Path $readOnlyFile -Name IsReadOnly -Value $false + Remove-Item -Path $readOnlyFile -Force + } + } + } + It "Verifies Invoke-WebRequest -Form sets Content-Disposition FileName and FileNameStar." { $ContentDisposition = [System.Net.Http.Headers.ContentDispositionHeaderValue]::new("attachment") $ContentDisposition.FileName = $fileName @@ -3568,6 +3596,33 @@ Describe "Invoke-RestMethod tests" -Tags "Feature", "RequireAdminOnWindows" { $result.Files[0].Content | Should -Match $file1Contents } + It "Verifies Invoke-RestMethod -Form supports read-only file values" { + # Create a read-only test file + $readOnlyFile = Join-Path $TestDrive "readonly-test.txt" + "ReadOnly test content" | Out-File -FilePath $readOnlyFile -Encoding utf8 + Set-ItemProperty -Path $readOnlyFile -Name IsReadOnly -Value $true + + try { + $form = @{TestFile = [System.IO.FileInfo]$readOnlyFile} + $uri = Get-WebListenerUrl -Test 'Multipart' + $result = Invoke-RestMethod -Uri $uri -Form $form -Method 'POST' + + $result.Headers.'Content-Type' | Should -Match 'multipart/form-data' + $result.Files.Count | Should -Be 1 + + $result.Files[0].Name | Should -Be "TestFile" + $result.Files[0].FileName | Should -Be "readonly-test.txt" + $result.Files[0].ContentType | Should -Be 'application/octet-stream' + $result.Files[0].Content | Should -Match "ReadOnly test content" + } finally { + # Clean up: remove read-only attribute before cleanup + if (Test-Path $readOnlyFile) { + Set-ItemProperty -Path $readOnlyFile -Name IsReadOnly -Value $false + Remove-Item -Path $readOnlyFile -Force + } + } + } + It "Verifies Invoke-RestMethod -Form sets Content-Disposition FileName and FileNameStar." { $ContentDisposition = [System.Net.Http.Headers.ContentDispositionHeaderValue]::new("attachment") $ContentDisposition.FileName = $fileName From 3f2f5165412ad1b470cca6d4ca298bad00dc0b40 Mon Sep 17 00:00:00 2001 From: yotsuda Date: Sat, 15 Nov 2025 17:34:31 +0900 Subject: [PATCH 2/4] Add FileShare.Read parameter as suggested by @iSazonov --- .../utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs index bf955f824cb..f1a455974b9 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs @@ -1995,7 +1995,7 @@ private static StreamContent GetMultipartStreamContent(object fieldName, Stream /// The file to use for the private static StreamContent GetMultipartFileContent(object fieldName, FileInfo file) { - StreamContent result = GetMultipartStreamContent(fieldName: fieldName, stream: new FileStream(file.FullName, FileMode.Open, FileAccess.Read)); + StreamContent result = GetMultipartStreamContent(fieldName: fieldName, stream: new FileStream(file.FullName, FileMode.Open, FileAccess.Read, FileShare.Read)); result.Headers.ContentDisposition.FileName = file.Name; result.Headers.ContentDisposition.FileNameStar = file.Name; From c8b734c3a0a1daec882a380eec80038a67e2f894 Mon Sep 17 00:00:00 2001 From: yotsuda Date: Sun, 16 Nov 2025 19:53:14 +0900 Subject: [PATCH 3/4] Remove manual cleanup for read-only files in WebCmdlets tests --- .../WebCmdlets.Tests.ps1 | 64 +++++++------------ 1 file changed, 24 insertions(+), 40 deletions(-) diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 index 861d0677927..dcf2d1da396 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 @@ -1,4 +1,4 @@ -# Copyright (c) Microsoft Corporation. +# Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # This is a Pester test suite which validate the Web cmdlets. @@ -1733,26 +1733,18 @@ Describe "Invoke-WebRequest tests" -Tags "Feature", "RequireAdminOnWindows" { "ReadOnly test content" | Out-File -FilePath $readOnlyFile -Encoding utf8 Set-ItemProperty -Path $readOnlyFile -Name IsReadOnly -Value $true - try { - $form = @{TestFile = [System.IO.FileInfo]$readOnlyFile} - $uri = Get-WebListenerUrl -Test 'Multipart' - $response = Invoke-WebRequest -Uri $uri -Form $form -Method 'POST' - $result = $response.Content | ConvertFrom-Json - - $result.Headers.'Content-Type' | Should -Match 'multipart/form-data' - $result.Files.Count | Should -Be 1 - - $result.Files[0].Name | Should -BeExactly "TestFile" - $result.Files[0].FileName | Should -BeExactly "readonly-test.txt" - $result.Files[0].ContentType | Should -BeExactly 'application/octet-stream' - $result.Files[0].Content | Should -Match "ReadOnly test content" - } finally { - # Clean up: remove read-only attribute before cleanup - if (Test-Path $readOnlyFile) { - Set-ItemProperty -Path $readOnlyFile -Name IsReadOnly -Value $false - Remove-Item -Path $readOnlyFile -Force - } - } + $form = @{TestFile = [System.IO.FileInfo]$readOnlyFile} + $uri = Get-WebListenerUrl -Test 'Multipart' + $response = Invoke-WebRequest -Uri $uri -Form $form -Method 'POST' + $result = $response.Content | ConvertFrom-Json + + $result.Headers.'Content-Type' | Should -Match 'multipart/form-data' + $result.Files.Count | Should -Be 1 + + $result.Files[0].Name | Should -BeExactly "TestFile" + $result.Files[0].FileName | Should -BeExactly "readonly-test.txt" + $result.Files[0].ContentType | Should -BeExactly 'application/octet-stream' + $result.Files[0].Content | Should -Match "ReadOnly test content" } It "Verifies Invoke-WebRequest -Form sets Content-Disposition FileName and FileNameStar." { @@ -3602,25 +3594,17 @@ Describe "Invoke-RestMethod tests" -Tags "Feature", "RequireAdminOnWindows" { "ReadOnly test content" | Out-File -FilePath $readOnlyFile -Encoding utf8 Set-ItemProperty -Path $readOnlyFile -Name IsReadOnly -Value $true - try { - $form = @{TestFile = [System.IO.FileInfo]$readOnlyFile} - $uri = Get-WebListenerUrl -Test 'Multipart' - $result = Invoke-RestMethod -Uri $uri -Form $form -Method 'POST' - - $result.Headers.'Content-Type' | Should -Match 'multipart/form-data' - $result.Files.Count | Should -Be 1 - - $result.Files[0].Name | Should -Be "TestFile" - $result.Files[0].FileName | Should -Be "readonly-test.txt" - $result.Files[0].ContentType | Should -Be 'application/octet-stream' - $result.Files[0].Content | Should -Match "ReadOnly test content" - } finally { - # Clean up: remove read-only attribute before cleanup - if (Test-Path $readOnlyFile) { - Set-ItemProperty -Path $readOnlyFile -Name IsReadOnly -Value $false - Remove-Item -Path $readOnlyFile -Force - } - } + $form = @{TestFile = [System.IO.FileInfo]$readOnlyFile} + $uri = Get-WebListenerUrl -Test 'Multipart' + $result = Invoke-RestMethod -Uri $uri -Form $form -Method 'POST' + + $result.Headers.'Content-Type' | Should -Match 'multipart/form-data' + $result.Files.Count | Should -Be 1 + + $result.Files[0].Name | Should -Be "TestFile" + $result.Files[0].FileName | Should -Be "readonly-test.txt" + $result.Files[0].ContentType | Should -Be 'application/octet-stream' + $result.Files[0].Content | Should -Match "ReadOnly test content" } It "Verifies Invoke-RestMethod -Form sets Content-Disposition FileName and FileNameStar." { From 8fe4fed9161936ad90ec067b3e0740ef5eba7fb1 Mon Sep 17 00:00:00 2001 From: yotsuda Date: Sun, 16 Nov 2025 22:51:56 +0900 Subject: [PATCH 4/4] Remove UTF-8 BOM from WebCmdlets.Tests.ps1 --- .../Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 index dcf2d1da396..7c0fffa5c4a 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 @@ -1,4 +1,4 @@ -# Copyright (c) Microsoft Corporation. +# Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # This is a Pester test suite which validate the Web cmdlets.