diff --git a/.azdevops/CI.yml b/.azdevops/CI.yml index b56f625..8c2b168 100644 --- a/.azdevops/CI.yml +++ b/.azdevops/CI.yml @@ -27,51 +27,46 @@ stages: displayName: Build module steps: - - pwsh: | - & $(Build.SourcesDirectory)\SimpleBuild.ps1 - displayName: Build Microsoft.PowerShell.Archive module - condition: succeededOrFailed() + - task: UseDotNet@2 + displayName: 'Get .NET 7.0 SDK' + inputs: + packageType: sdk + version: 7.x + includePreviewVersions: true - pwsh: | - dir "$(BuildOutDir)\*" -Recurse - displayName: Show BuildOutDirectory - - - template: Sbom.yml@ComplianceRepo - parameters: - BuildDropPath: "$(BuildOutDir)" - Build_Repository_Uri: 'https://github.com/PowerShell/Microsoft.PowerShell.Archive' - PackageName: $(PackageName) - PackageVersion: $(PackageVersion) - - - pwsh: | - dir "$(BuildOutDir)\*" -Recurse - displayName: Show BuildOutDirectory + & "$(Build.SourcesDirectory)\Build.ps1" + displayName: Build Microsoft.PowerShell.Archive module - - pwsh: | - $signSrcPath = "$(BuildOutDir)" - # Set signing src path variable - $vstsCommandString = "vso[task.setvariable variable=signSrcPath]${signSrcPath}" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - $signOutPath = "$(Build.SourcesDirectory)\signed\Microsoft.PowerShell.Archive" - $null = New-Item -ItemType Directory -Path $signOutPath - # Set signing out path variable - $vstsCommandString = "vso[task.setvariable variable=signOutPath]${signOutPath}" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - # Set path variable for guardian codesign validation - $vstsCommandString = "vso[task.setvariable variable=GDN_CODESIGN_TARGETDIRECTORY]${signOutPath}" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - displayName: Setup variables for signing + - task: CopyFiles@2 + displayName: 'Copy build' + inputs: + sourceFolder: '$(BuildOutDir)' + contents: '**' + targetFolder: '$(Build.ArtifactStagingDirectory)/Microsoft.PowerShell.Archive' - - pwsh: | - Copy-Item -Path "$(signSrcPath)\*" -Destination "$(signOutPath)" - displayName: Fake Signing + - publish: '$(Build.ArtifactStagingDirectory)/Microsoft.PowerShell.Archive' + displayName: 'Publish module build' + artifact: ModuleBuild - - pwsh: | - Compress-Archive -Path "$(signOutPath)\*" -DestinationPath "$(System.ArtifactsDirectory)\Microsoft.PowerShell.Archive.zip" - displayName: Create Microsoft.PowerShell.Archive.zip +- stage: Test + dependsOn: Build + displayName: Run tests + jobs: + - template: TestsTemplate.yml + parameters: + vmImageName: windows-2019 + jobName: run_test_windows + jobDisplayName: Run Windows tests - - publish: $(System.ArtifactsDirectory)\Microsoft.PowerShell.Archive.zip - artifact: SignedModule + - template: TestsTemplate.yml + parameters: + vmImageName: ubuntu-latest + jobName: run_test_linux + jobDisplayName: Run Linux tests + + - template: TestsTemplate.yml + parameters: + vmImageName: macos-latest + jobName: run_test_macos + jobDisplayName: Run macOS tests diff --git a/.azdevops/ReleaseBuildPipeline.yml b/.azdevops/ReleaseBuildPipeline.yml new file mode 100644 index 0000000..cf05994 --- /dev/null +++ b/.azdevops/ReleaseBuildPipeline.yml @@ -0,0 +1,138 @@ +name: Microsoft.PowerShell.Archive-$(Build.BuildId) +trigger: none + +pr: none + +variables: + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + POWERSHELL_TELEMETRY_OPTOUT: 1 + +resources: + repositories: + - repository: ComplianceRepo + type: github + endpoint: ComplianceGHRepo + name: PowerShell/compliance + ref: master + +stages: +- stage: Build + displayName: Build + pool: + name: 1ES + demands: + - ImageOverride -equals PSMMS2019-Secure + jobs: + - job: Build_Job + displayName: Build Microsoft.PowerShell.Archive + variables: + - group: ESRP + steps: + - checkout: self + + - task: UseDotNet@2 + displayName: 'Get .NET 7.0 SDK' + inputs: + packageType: sdk + version: 7.x + includePreviewVersions: true + + - pwsh: | + & $(Build.SourcesDirectory)/Microsoft.PowerShell.Archive/SimpleBuild.ps1 + displayName: Build Microsoft.PowerShell.Archive module + + - pwsh: | + Get-ChildItem "$(BuildOutDir)\*" -Recurse | Write-Verbose -Verbose + displayName: Show BuildOutDirectory + + - pwsh: | + $signSrcPath = "$(BuildOutDir)" + # Set signing src path variable + $vstsCommandString = "vso[task.setvariable variable=signSrcPath]${signSrcPath}" + Write-Host "sending " + $vstsCommandString + Write-Host "##$vstsCommandString" + # Get the module version + $ManifestPath = Join-Path $(BuildOutDir) "Microsoft.PowerShell.Archive.psd1" + $ManifestData = Import-PowerShellDataFile -Path $ManifestPath + $Version = $ManifestData.ModuleVersion + $signOutPath = "$(Build.SourcesDirectory)\signed\Microsoft.PowerShell.Archive\${Version}" + $null = New-Item -ItemType Directory -Path $signOutPath + # Set signing out path variable + $vstsCommandString = "vso[task.setvariable variable=signOutPath]${signOutPath}" + Write-Host "sending " + $vstsCommandString + Write-Host "##$vstsCommandString" + # Set path variable for guardian codesign validation + $vstsCommandString = "vso[task.setvariable variable=GDN_CODESIGN_TARGETDIRECTORY]${signOutPath}" + Write-Host "sending " + $vstsCommandString + Write-Host "##$vstsCommandString" + displayName: Setup variables for signing + + - checkout: ComplianceRepo + + - task: UseDotNet@2 + displayName: 'Get .NET 2.1 SDK' + inputs: + packageType: sdk + version: 2.x + includePreviewVersions: true + + - template: EsrpSign.yml@ComplianceRepo + parameters: + # the folder which contains the binaries to sign + buildOutputPath: $(signSrcPath) + # the location to put the signed output + signOutputPath: $(signOutPath) + # the certificate ID to use + certificateId: "CP-230012" + # the file pattern to use, comma separated + pattern: '*.psd1,*.dll' + + - template: Sbom.yml@ComplianceRepo + parameters: + BuildDropPath: $(signOutPath) + Build_Repository_Uri: 'https://github.com/PowerShell/Microsoft.PowerShell.Archive' + + - pwsh: | + Get-ChildItem $(signOutPath) -Recurse | Write-Output + + - pwsh: | + Set-Location "$(Build.SourcesDirectory)" + # signOutPath points to directory with version number -- we want to point to the parent of that directory + $ModulePath = Split-Path $(signOutPath) -Parent + $(Build.SourcesDirectory)/Microsoft.PowerShell.Archive/.azdevops/SignAndPackageModule.ps1 -SignedPath $ModulePath + Get-ChildItem -recurse -file -name | Write-Verbose -Verbose + displayName: package build + + - publish: "$(signSrcPath)" + artifact: build + displayName: Publish build + +- stage: compliance + displayName: Compliance + dependsOn: Build + jobs: + - job: Compliance_Job + pool: + name: 1ES # Package ES CodeHub Lab E + steps: + - checkout: self + - checkout: ComplianceRepo + - download: current + artifact: build + + - pwsh: | + Get-ChildItem -Path "$(Pipeline.Workspace)\build" -Recurse + displayName: Capture downloaded artifacts + - template: script-module-compliance.yml@ComplianceRepo + parameters: + # component-governance + sourceScanPath: '$(Build.SourcesDirectory)\Microsoft.PowerShell.Archive\src' + # credscan + suppressionsFile: '' + # TermCheck + optionsRulesDBPath: '' + optionsFTPath: '' + # tsa-upload + codeBaseName: 'PSNativeCommandProxy_2020' + # selections + APIScan: false # set to false when not using Windows APIs. diff --git a/.azdevops/RunTests.ps1 b/.azdevops/RunTests.ps1 new file mode 100644 index 0000000..0361d4f --- /dev/null +++ b/.azdevops/RunTests.ps1 @@ -0,0 +1,29 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# Import the built module +Import-Module "$env:PIPELINE_WORKSPACE/ModuleBuild/Microsoft.PowerShell.Archive.psd1" + +Get-ChildItem "$env:PIPELINE_WORKSPACE/ModuleBuild" | Write-Verbose -Verbose + +$pesterMinVersion = "5.3.0" +$pesterMaxVersion = "5.3.9" + +# If Pester 5.3.x is not installed, install it +$pesterModule = Get-InstalledModule -Name "Pester" -MinimumVersion $pesterMinVersion -MaximumVersion $pesterMaxVersion +if ($null -eq $pesterModule) { + Install-Module -Name "Pester" -MinimumVersion $pesterMinVersion -MaximumVersion $pesterMaxVersion -Force +} + +# Load Pester +Import-Module -Name "Pester" -MinimumVersion $pesterMinVersion -MaximumVersion $pesterMaxVersion + +# Run tests +$OutputFile = "$PWD/build-unit-tests.xml" +$results = $null +$results = Invoke-Pester -Script ./Tests/Compress-Archive.Tests.ps1 -OutputFile $OutputFile -PassThru -OutputFormat NUnitXml -Show Failed, Context, Describe, Fails +Write-Host "##vso[artifact.upload containerfolder=testResults;artifactname=testResults]$OutputFile" +if(!$results -or $results.FailedCount -gt 0 -or !$results.TotalCount) +{ + throw "Build or tests failed. Passed: $($results.PassedCount) Failed: $($results.FailedCount) Total: $($results.TotalCount)" +} \ No newline at end of file diff --git a/.azdevops/SignAndPackageModule.ps1 b/.azdevops/SignAndPackageModule.ps1 new file mode 100644 index 0000000..06427ac --- /dev/null +++ b/.azdevops/SignAndPackageModule.ps1 @@ -0,0 +1,44 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +[CmdletBinding(SupportsShouldProcess=$true)] +param ( + [string]$SignedPath + ) + + +$root = (Resolve-Path -Path "${PSScriptRoot}/../")[0] +$Name = "Microsoft.PowerShell.Archive" +$BuildOutputDir = Join-Path $root "\src\bin\Release" +$ManifestPath = "${BuildOutputDir}\${Name}.psd1" +$ManifestData = Import-PowerShellDataFile -Path $ManifestPath +$Version = $ManifestData.ModuleVersion + +# this takes the files for the module and publishes them to a created, local repository +# so the nupkg can be used to publish to the PSGallery +function Export-Module +{ + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingWriteHost", "")] + param() + $packageRoot = $SignedPath + + if ( -not (Test-Path $packageRoot)) { + throw "'$PubDir' does not exist" + } + + # now constuct a nupkg by registering a local repository and calling publish module + $repoName = [guid]::newGuid().ToString("N") + Register-PSRepository -Name $repoName -SourceLocation $packageRoot -InstallationPolicy Trusted + Publish-Module -Path $packageRoot -Repository $repoName + Unregister-PSRepository -Name $repoName + Get-ChildItem -Recurse -Name $packageRoot | Write-Verbose + $nupkgName = "{0}.{1}-preview1.nupkg" -f ${Name},${Version} + $nupkgPath = Join-Path $packageRoot $nupkgName + if ($env:TF_BUILD) { + # In Azure DevOps + Write-Host "##vso[artifact.upload containerfolder=$nupkgName;artifactname=$nupkgName;]$nupkgPath" + } +} + +# The SBOM should already be in -SignedPath, so there is no need to copy it + +Export-Module diff --git a/.azdevops/TestsTemplate.yml b/.azdevops/TestsTemplate.yml new file mode 100644 index 0000000..0876709 --- /dev/null +++ b/.azdevops/TestsTemplate.yml @@ -0,0 +1,94 @@ +parameters: + - name: vmImageName + type: string + default: 'windows-2019' + + - name: jobName + type: string + default: 'run_test_windows' + + - name: jobDisplayName + type: string + default: 'Run test' + +jobs: + - job: '${{ parameters.jobName }}' + pool: + vmImage: ${{ parameters.vmImageName }} + displayName: ${{ parameters.jobDisplayName }} + steps: + - download: current + artifact: ModuleBuild + + - pwsh: | + Write-Output ${{ parameters.vmImageName }} + + if ("${{ parameters.vmImageName }}" -like 'windows-*') + { + $url = "https://github.com/PowerShell/PowerShell/releases/download/v7.3.0-preview.6/PowerShell-7.3.0-preview.6-win-x64.zip" + $downloadFilename = "pwsh_download.msi" + } + + if ("${{ parameters.vmImageName }}" -like 'macos-*') + { + $url = "https://github.com/PowerShell/PowerShell/releases/download/v7.3.0-preview.6/powershell-7.3.0-preview.6-osx-x64.pkg" + $downloadFilename = "pwsh_download.pkg" + } + if ("${{ parameters.vmImageName }}" -like 'ubuntu-*') + { + $url = "https://github.com/PowerShell/PowerShell/releases/download/v7.3.0-preview.6/powershell-7.3.0-preview.6-linux-x64.tar.gz" + $downloadFilename = "pwsh_download.tar.gz" + } + + $downloadDestination = Join-Path $pwd $downloadFilename + Invoke-WebRequest -Uri $url -OutFile $downloadDestination + + # Installation steps for windows + if ("${{ parameters.vmImageName }}" -like 'windows-*') { + Expand-Archive -Path $downloadDestination -DestinationPath "pwsh-preview" + $powerShellPreview = Join-Path $pwd "pwsh-preview" "pwsh.exe" + } + if ("${{ parameters.vmImageName }}" -like 'ubuntu-*') + { + gunzip -d $downloadDestination + $downloadDestination = $downloadDestination.Replace(".gz", "") + mkdir "pwsh-preview" + tar -x -f $downloadDestination -C "pwsh-preview" + $powerShellPreview = Join-Path $pwd "pwsh-preview" "pwsh" + } + if ("${{ parameters.vmImageName }}" -like 'macos-*') + { + sudo xattr -rd com.apple.quarantine "${downloadDestination}" + sudo installer -pkg "${downloadDestination}" -target / + $powerShellPreview = "pwsh-preview" + } + # Write the location of PowerShell Preview + Write-Host "##vso[task.setvariable variable=PowerShellPreviewExecutablePath;]$powershellPreview" + displayName: Download and Install PowerShell Preview + + - pwsh: | + $destination = Join-Path $pwd "7z.exe" + $installUrl = "https://www.7-zip.org/a/7z2201-x64.exe" + Invoke-WebRequest -Uri $installUrl -OutFile $destination + # Run the installer in silent mode + .$destination /S /D="C:\Program Files\7-Zip" + displayName: Install 7-zip + condition: and(succeeded(), startswith('${{ parameters.vmImageName }}', 'windows')) + + - pwsh: | + if ("${{ parameters.vmImageName }}" -like 'windows-*') + { + # Add 7-zip to PATH on Windows + [System.Environment]::SetEnvironmentVariable('PATH',$Env:PATH+';C:\Program Files\7-zip') + } + "$(PowerShellPreviewExecutablePath) .azdevops/RunTests.ps1" | Invoke-Expression + displayName: Run Tests + + - task: PublishTestResults@2 + displayName: 'Publish Test Results **/*tests.xml' + inputs: + testResultsFormat: NUnit + testResultsFiles: '**/*tests.xml' + testRunTitle: 'Build Unit Tests' + continueOnError: true + condition: succeededOrFailed() diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..e342d6f --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +CHANGELOG.md merge=union +* text=auto +*.png binary +*.rtf binary +*.sh text eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6fbb448 --- /dev/null +++ b/.gitignore @@ -0,0 +1,91 @@ +bin/ +obj/ +.ionide/ +project.lock.json +*-tests.xml +/debug/ +/staging/ +/Packages/ +*.nuget.props + +# VS auto-generated solution files for project.json solutions +*.xproj +*.xproj.user +*.suo + +# VS auto-generated files for csproj files +*.csproj.user + +# Visual Studio IDE directory +.vs/ + +# Visual Studio Configuration Settings +src/Properties/ + +# VSCode directories that are not at the repository root +/**/.vscode/ + +# Project Rider IDE files +.idea.powershell/ + +# Ignore executables +*.exe +*.msi +*.appx +*.msix + +# Ignore binaries and symbols +*.pdb +*.dll +*.wixpdb + +# Ignore packages +*.deb +*.tar.gz +*.zip +*.rpm +*.pkg +*.nupkg +*.AppImage + +# default location for produced nuget packages +/nuget-artifacts + +# resgen output +gen + +# Per repo profile +.profile.ps1 + +# macOS +.DS_Store +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk +.AppleDouble +.LSOverride + +# TestsResults +TestsResults*.xml +ParallelXUnitResults.xml +xUnitResults.xml + +# Resharper settings +PowerShell.sln.DotSettings.user +*.msp +StyleCop.Cache + +# Ignore SelfSignedCertificate autogenerated files +test/tools/Modules/SelfSignedCertificate/ + +# BenchmarkDotNet artifacts +test/perf/BenchmarkDotNet.Artifacts/ \ No newline at end of file diff --git a/.globalconfig b/.globalconfig new file mode 100644 index 0000000..c51a485 --- /dev/null +++ b/.globalconfig @@ -0,0 +1,2253 @@ +is_global = true + +# CA1000: Do not declare static members on generic types +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1000 +dotnet_diagnostic.CA1000.severity = warning +dotnet_code_quality.CA1000.api_surface = all + +# CA1001: Types that own disposable fields should be disposable +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1001 +dotnet_diagnostic.CA1001.severity = silent + +# CA1002: Do not expose generic lists +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1002 +dotnet_diagnostic.CA1002.severity = none + +# CA1003: Use generic event handler instances +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1003 +dotnet_diagnostic.CA1003.severity = warning +dotnet_code_quality.CA1003.api_surface = private, internal + +# CA1005: Avoid excessive parameters on generic types +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1005 +dotnet_diagnostic.CA1005.severity = none + +# CA1008: Enums should have zero value +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1008 +dotnet_diagnostic.CA1008.severity = none +dotnet_code_quality.CA1008.api_surface = public + +# CA1010: Generic interface should also be implemented +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1010 +dotnet_diagnostic.CA1010.severity = silent +dotnet_code_quality.CA1010.api_surface = public + +# CA1012: Abstract types should not have public constructors +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1012 +dotnet_diagnostic.CA1012.severity = warning +dotnet_code_quality.CA1012.api_surface = all + +# CA1014: Mark assemblies with CLSCompliant +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1014 +dotnet_diagnostic.CA1014.severity = none + +# CA1016: Mark assemblies with assembly version +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1016 +dotnet_diagnostic.CA1016.severity = warning + +# CA1017: Mark assemblies with ComVisible +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1017 +dotnet_diagnostic.CA1017.severity = none + +# CA1018: Mark attributes with AttributeUsageAttribute +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1018 +dotnet_diagnostic.CA1018.severity = warning + +# CA1019: Define accessors for attribute arguments +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1019 +dotnet_diagnostic.CA1019.severity = none + +# CA1021: Avoid out parameters +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1021 +dotnet_diagnostic.CA1021.severity = none + +# CA1024: Use properties where appropriate +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1024 +dotnet_diagnostic.CA1024.severity = none +dotnet_code_quality.CA1024.api_surface = public + +# CA1027: Mark enums with FlagsAttribute +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1027 +dotnet_diagnostic.CA1027.severity = none +dotnet_code_quality.CA1027.api_surface = public + +# CA1028: Enum Storage should be Int32 +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1028 +dotnet_diagnostic.CA1028.severity = none +dotnet_code_quality.CA1028.api_surface = public + +# CA1030: Use events where appropriate +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1030 +dotnet_diagnostic.CA1030.severity = none +dotnet_code_quality.CA1030.api_surface = public + +# CA1031: Do not catch general exception types +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1031 +dotnet_diagnostic.CA1031.severity = none + +# CA1032: Implement standard exception constructors +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1032 +dotnet_diagnostic.CA1032.severity = none + +# CA1033: Interface methods should be callable by child types +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1033 +dotnet_diagnostic.CA1033.severity = none + +# CA1034: Nested types should not be visible +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1034 +dotnet_diagnostic.CA1034.severity = none + +# CA1036: Override methods on comparable types +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1036 +dotnet_diagnostic.CA1036.severity = silent +dotnet_code_quality.CA1036.api_surface = public + +# CA1040: Avoid empty interfaces +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1040 +dotnet_diagnostic.CA1040.severity = none +dotnet_code_quality.CA1040.api_surface = public + +# CA1041: Provide ObsoleteAttribute message +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1041 +dotnet_diagnostic.CA1041.severity = warning +dotnet_code_quality.CA1041.api_surface = public + +# CA1043: Use Integral Or String Argument For Indexers +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1043 +dotnet_diagnostic.CA1043.severity = warning +dotnet_code_quality.CA1043.api_surface = all + +# CA1044: Properties should not be write only +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1044 +dotnet_diagnostic.CA1044.severity = none +dotnet_code_quality.CA1044.api_surface = public + +# CA1045: Do not pass types by reference +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1045 +dotnet_diagnostic.CA1045.severity = none + +# CA1046: Do not overload equality operator on reference types +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1046 +dotnet_diagnostic.CA1046.severity = none + +# CA1047: Do not declare protected member in sealed type +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1047 +dotnet_diagnostic.CA1047.severity = warning + +# CA1050: Declare types in namespaces +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1050 +dotnet_diagnostic.CA1050.severity = warning + +# CA1051: Do not declare visible instance fields +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1051 +dotnet_diagnostic.CA1051.severity = silent +dotnet_code_quality.CA1051.api_surface = public + +# CA1052: Static holder types should be Static or NotInheritable +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1052 +dotnet_diagnostic.CA1052.severity = warning +dotnet_code_quality.CA1052.api_surface = all + +# CA1054: URI-like parameters should not be strings +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1054 +dotnet_diagnostic.CA1054.severity = none +dotnet_code_quality.CA1054.api_surface = public + +# CA1055: URI-like return values should not be strings +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1055 +dotnet_diagnostic.CA1055.severity = none +dotnet_code_quality.CA1055.api_surface = public + +# CA1056: URI-like properties should not be strings +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1056 +dotnet_diagnostic.CA1056.severity = none +dotnet_code_quality.CA1056.api_surface = public + +# CA1058: Types should not extend certain base types +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1058 +dotnet_diagnostic.CA1058.severity = none +dotnet_code_quality.CA1058.api_surface = public + +# CA1060: Move pinvokes to native methods class +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1060 +dotnet_diagnostic.CA1060.severity = none + +# CA1061: Do not hide base class methods +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1061 +dotnet_diagnostic.CA1061.severity = warning + +# CA1062: Validate arguments of public methods +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1062 +dotnet_diagnostic.CA1062.severity = none + +# CA1063: Implement IDisposable Correctly +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1063 +dotnet_diagnostic.CA1063.severity = none +dotnet_code_quality.CA1063.api_surface = public + +# CA1064: Exceptions should be public +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1064 +dotnet_diagnostic.CA1064.severity = none + +# CA1065: Do not raise exceptions in unexpected locations +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1065 +dotnet_diagnostic.CA1065.severity = warning + +# CA1066: Implement IEquatable when overriding Object.Equals +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1066 +dotnet_diagnostic.CA1066.severity = none + +# CA1067: Override Object.Equals(object) when implementing IEquatable +# # https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1067 +dotnet_diagnostic.CA1067.severity = warning + +# CA1068: CancellationToken parameters must come last +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1068 +dotnet_diagnostic.CA1068.severity = warning + +# CA1069: Enums values should not be duplicated +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1069 +dotnet_diagnostic.CA1069.severity = suggestion + +# CA1070: Do not declare event fields as virtual +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1070 +dotnet_diagnostic.CA1070.severity = warning + +# CA1200: Avoid using cref tags with a prefix +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1200 +dotnet_diagnostic.CA1200.severity = silent + +# CA1303: Do not pass literals as localized parameters +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1303 +dotnet_diagnostic.CA1303.severity = none + +# CA1304: Specify CultureInfo +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1304 +dotnet_diagnostic.CA1304.severity = silent + +# CA1305: Specify IFormatProvider +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1305 +dotnet_diagnostic.CA1305.severity = silent + +# CA1307: Specify StringComparison for clarity +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1307 +dotnet_diagnostic.CA1307.severity = none + +# CA1308: Normalize strings to uppercase +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1308 +dotnet_diagnostic.CA1308.severity = none + +# CA1309: Use ordinal string comparison +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1309 +dotnet_diagnostic.CA1309.severity = silent + +# CA1310: Specify StringComparison for correctness +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1310 +dotnet_diagnostic.CA1310.severity = silent + +# CA1401: P/Invokes should not be visible +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1401 +dotnet_diagnostic.CA1401.severity = warning + +# CA1416: Validate platform compatibility +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416 +dotnet_diagnostic.CA1416.severity = warning + +# CA1417: Do not use 'OutAttribute' on string parameters for P/Invokes +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1417 +dotnet_diagnostic.CA1417.severity = warning + +# CA1418: Use valid platform string +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1418 +dotnet_diagnostic.CA1418.severity = warning + +# CA1501: Avoid excessive inheritance +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1501 +dotnet_diagnostic.CA1501.severity = none + +# CA1502: Avoid excessive complexity +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1502 +dotnet_diagnostic.CA1502.severity = none + +# CA1505: Avoid unmaintainable code +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1505 +dotnet_diagnostic.CA1505.severity = none + +# CA1506: Avoid excessive class coupling +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1506 +dotnet_diagnostic.CA1506.severity = none + +# CA1507: Use nameof to express symbol names +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1507 +dotnet_diagnostic.CA1507.severity = suggestion + +# CA1508: Avoid dead conditional code +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1508 +dotnet_diagnostic.CA1508.severity = none + +# CA1509: Invalid entry in code metrics rule specification file +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1509 +dotnet_diagnostic.CA1509.severity = none + +# CA1700: Do not name enum values 'Reserved' +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1700 +dotnet_diagnostic.CA1700.severity = none + +# CA1707: Identifiers should not contain underscores +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1707 +dotnet_diagnostic.CA1707.severity = silent + +# CA1708: Identifiers should differ by more than case +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1708 +dotnet_diagnostic.CA1708.severity = silent +dotnet_code_quality.CA1708.api_surface = public + +# CA1710: Identifiers should have correct suffix +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1710 +dotnet_diagnostic.CA1710.severity = silent +dotnet_code_quality.CA1710.api_surface = public + +# CA1711: Identifiers should not have incorrect suffix +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1711 +dotnet_diagnostic.CA1711.severity = silent +dotnet_code_quality.CA1711.api_surface = public + +# CA1712: Do not prefix enum values with type name +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1712 +dotnet_diagnostic.CA1712.severity = silent + +# CA1713: Events should not have 'Before' or 'After' prefix +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1713 +dotnet_diagnostic.CA1713.severity = none + +# CA1715: Identifiers should have correct prefix +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1715 +dotnet_diagnostic.CA1715.severity = silent +dotnet_code_quality.CA1715.api_surface = public + +# CA1716: Identifiers should not match keywords +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1716 +dotnet_diagnostic.CA1716.severity = silent +dotnet_code_quality.CA1716.api_surface = public + +# CA1720: Identifier contains type name +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1720 +dotnet_diagnostic.CA1720.severity = silent +dotnet_code_quality.CA1720.api_surface = public + +# CA1721: Property names should not match get methods +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1721 +dotnet_diagnostic.CA1721.severity = none +dotnet_code_quality.CA1721.api_surface = public + +# CA1724: Type names should not match namespaces +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1724 +dotnet_diagnostic.CA1724.severity = none + +# CA1725: Parameter names should match base declaration +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1725 +dotnet_diagnostic.CA1725.severity = silent +dotnet_code_quality.CA1725.api_surface = public + +# CA1801: Review unused parameters +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1801 +dotnet_diagnostic.CA1801.severity = none +dotnet_code_quality.CA1801.api_surface = all + +# CA1802: Use literals where appropriate +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1802 +dotnet_diagnostic.CA1802.severity = none +dotnet_code_quality.CA1802.api_surface = public + +# CA1805: Do not initialize unnecessarily +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1805 +dotnet_diagnostic.CA1805.severity = suggestion + +# CA1806: Do not ignore method results +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1806 +dotnet_diagnostic.CA1806.severity = suggestion + +# CA1810: Initialize reference type static fields inline +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1810 +dotnet_diagnostic.CA1810.severity = none + +# CA1812: Avoid uninstantiated internal classes +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1812 +dotnet_diagnostic.CA1812.severity = warning + +# CA1813: Avoid unsealed attributes +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1813 +dotnet_diagnostic.CA1813.severity = none + +# CA1814: Prefer jagged arrays over multidimensional +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1814 +dotnet_diagnostic.CA1814.severity = none + +# CA1815: Override equals and operator equals on value types +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1815 +dotnet_diagnostic.CA1815.severity = none +dotnet_code_quality.CA1815.api_surface = public + +# CA1816: Dispose methods should call SuppressFinalize +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1816 +dotnet_diagnostic.CA1816.severity = warning + +# CA1819: Properties should not return arrays +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1819 +dotnet_diagnostic.CA1819.severity = none +dotnet_code_quality.CA1819.api_surface = public + +# CA1820: Test for empty strings using string length +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1820 +dotnet_diagnostic.CA1820.severity = none + +# CA1821: Remove empty Finalizers +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1821 +dotnet_diagnostic.CA1821.severity = warning + +# CA1822: Mark members as static +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822 +dotnet_diagnostic.CA1822.severity = warning +dotnet_code_quality.CA1822.api_surface = private + +# CA1823: Avoid unused private fields +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1823 +dotnet_diagnostic.CA1823.severity = none + +# CA1824: Mark assemblies with NeutralResourcesLanguageAttribute +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1824 +dotnet_diagnostic.CA1824.severity = warning + +# CA1825: Avoid zero-length array allocations +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1825 +dotnet_diagnostic.CA1825.severity = warning + +# CA1826: Do not use Enumerable methods on indexable collections +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1826 +dotnet_diagnostic.CA1826.severity = warning + +# CA1827: Do not use Count() or LongCount() when Any() can be used +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1827 +dotnet_diagnostic.CA1827.severity = warning + +# CA1828: Do not use CountAsync() or LongCountAsync() when AnyAsync() can be used +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1828 +dotnet_diagnostic.CA1828.severity = warning + +# CA1829: Use Length/Count property instead of Count() when available +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1829 +dotnet_diagnostic.CA1829.severity = warning + +# CA1830: Prefer strongly-typed Append and Insert method overloads on StringBuilder +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1830 +dotnet_diagnostic.CA1830.severity = warning + +# CA1831: Use AsSpan or AsMemory instead of Range-based indexers when appropriate +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1831 +dotnet_diagnostic.CA1831.severity = warning + +# CA1832: Use AsSpan or AsMemory instead of Range-based indexers when appropriate +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1832 +dotnet_diagnostic.CA1832.severity = warning + +# CA1833: Use AsSpan or AsMemory instead of Range-based indexers when appropriate +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1833 +dotnet_diagnostic.CA1833.severity = warning + +# CA1834: Consider using 'StringBuilder.Append(char)' when applicable +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1834 +dotnet_diagnostic.CA1834.severity = warning + +# CA1835: Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1835 +dotnet_diagnostic.CA1835.severity = suggestion + +# CA1836: Prefer IsEmpty over Count +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1836 +dotnet_diagnostic.CA1836.severity = warning + +# CA1837: Use 'Environment.ProcessId' +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1837 +dotnet_diagnostic.CA1837.severity = warning + +# CA1838: Avoid 'StringBuilder' parameters for P/Invokes +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1838 +dotnet_diagnostic.CA1838.severity = silent + +# CA1839: Use 'Environment.ProcessPath' +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1839 +dotnet_diagnostic.CA1839.severity = warning + +# CA1840: Use 'Environment.CurrentManagedThreadId' +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1840 +dotnet_diagnostic.CA1840.severity = warning + +# CA1841: Prefer Dictionary.Contains methods +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1841 +dotnet_diagnostic.CA1841.severity = warning + +# CA1842: Do not use 'WhenAll' with a single task +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1842 +dotnet_diagnostic.CA1842.severity = warning + +# CA1843: Do not use 'WaitAll' with a single task +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1843 +dotnet_diagnostic.CA1843.severity = warning + +# CA1844: Provide memory-based overrides of async methods when subclassing 'Stream' +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1844 +dotnet_diagnostic.CA1844.severity = warning + +# CA1845: Use span-based 'string.Concat' +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1845 +dotnet_diagnostic.CA1845.severity = warning + +# CA1846: Prefer 'AsSpan' over 'Substring' +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1846 +dotnet_diagnostic.CA1846.severity = warning + +# CA1847: Use char literal for a single character lookup +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1847 +dotnet_diagnostic.CA1847.severity = warning + +# CA2000: Dispose objects before losing scope +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000 +dotnet_diagnostic.CA2000.severity = none + +# CA2002: Do not lock on objects with weak identity +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2002 +dotnet_diagnostic.CA2002.severity = none + +# CA2007: Consider calling ConfigureAwait on the awaited task +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007 +dotnet_diagnostic.CA2007.severity = none + +# CA2008: Do not create tasks without passing a TaskScheduler +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2008 +dotnet_diagnostic.CA2008.severity = none + +# CA2009: Do not call ToImmutableCollection on an ImmutableCollection value +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2009 +dotnet_diagnostic.CA2009.severity = warning + +# CA2011: Avoid infinite recursion +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2011 +dotnet_diagnostic.CA2011.severity = warning + +# CA2012: Use ValueTasks correctly +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2012 +dotnet_diagnostic.CA2012.severity = warning + +# CA2013: Do not use ReferenceEquals with value types +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2013 +dotnet_diagnostic.CA2013.severity = warning + +# CA2014: Do not use stackalloc in loops +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2014 +dotnet_diagnostic.CA2014.severity = warning + +# CA2015: Do not define finalizers for types derived from MemoryManager +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2015 +dotnet_diagnostic.CA2015.severity = warning + +# CA2016: Forward the 'CancellationToken' parameter to methods that take one +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2016 +dotnet_diagnostic.CA2016.severity = suggestion + +# CA2100: Review SQL queries for security vulnerabilities +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2100 +dotnet_diagnostic.CA2100.severity = none + +# CA2101: Specify marshaling for P/Invoke string arguments +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2101 +dotnet_diagnostic.CA2101.severity = suggestion + +# CA2109: Review visible event handlers +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2109 +dotnet_diagnostic.CA2109.severity = none + +# CA2119: Seal methods that satisfy private interfaces +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2119 +dotnet_diagnostic.CA2119.severity = none + +# CA2153: Do Not Catch Corrupted State Exceptions +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2153 +dotnet_diagnostic.CA2153.severity = none + +# CA2200: Rethrow to preserve stack details +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2200 +dotnet_diagnostic.CA2200.severity = warning + +# CA2201: Do not raise reserved exception types +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2201 +dotnet_diagnostic.CA2201.severity = silent + +# CA2207: Initialize value type static fields inline +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2207 +dotnet_diagnostic.CA2207.severity = warning + +# CA2208: Instantiate argument exceptions correctly +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2208 +dotnet_diagnostic.CA2208.severity = suggestion +dotnet_code_quality.CA2208.api_surface = all + +# CA2211: Non-constant fields should not be visible +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2211 +dotnet_diagnostic.CA2211.severity = warning + +# CA2213: Disposable fields should be disposed +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2213 +dotnet_diagnostic.CA2213.severity = none + +# CA2214: Do not call overridable methods in constructors +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2214 +dotnet_diagnostic.CA2214.severity = none + +# CA2215: Dispose methods should call base class dispose +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2215 +dotnet_diagnostic.CA2215.severity = silent + +# CA2216: Disposable types should declare finalizer +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2216 +dotnet_diagnostic.CA2216.severity = warning + +# CA2217: Do not mark enums with FlagsAttribute +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2217 +dotnet_diagnostic.CA2217.severity = none +dotnet_code_quality.CA2217.api_surface = public + +# CA2218: Override GetHashCode on overriding Equals +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2218 +dotnet_diagnostic.CA2218.severity = suggestion + +# CA2219: Do not raise exceptions in finally clauses +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2219 +dotnet_diagnostic.CA2219.severity = suggestion + +# CA2224: Override Equals on overloading operator equals +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2224 +dotnet_diagnostic.CA2224.severity = suggestion + +# CA2225: Operator overloads have named alternates +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2225 +dotnet_diagnostic.CA2225.severity = none +dotnet_code_quality.CA2225.api_surface = public + +# CA2226: Operators should have symmetrical overloads +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2226 +dotnet_diagnostic.CA2226.severity = none +dotnet_code_quality.CA2226.api_surface = public + +# CA2227: Collection properties should be read only +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2227 +dotnet_diagnostic.CA2227.severity = none + +# CA2229: Implement serialization constructors +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2229 +dotnet_diagnostic.CA2229.severity = silent + +# CA2231: Overload operator equals on overriding value type Equals +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2231 +dotnet_diagnostic.CA2231.severity = suggestion +dotnet_code_quality.CA2231.api_surface = public + +# CA2234: Pass system uri objects instead of strings +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2234 +dotnet_diagnostic.CA2234.severity = none +dotnet_code_quality.CA2234.api_surface = public + +# CA2235: Mark all non-serializable fields +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2235 +dotnet_diagnostic.CA2235.severity = none + +# CA2237: Mark ISerializable types with serializable +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2237 +dotnet_diagnostic.CA2237.severity = none + +# CA2241: Provide correct arguments to formatting methods +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2241 +dotnet_diagnostic.CA2241.severity = suggestion + +# CA2242: Test for NaN correctly +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2242 +dotnet_diagnostic.CA2242.severity = suggestion + +# CA2243: Attribute string literals should parse correctly +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2243 +dotnet_diagnostic.CA2243.severity = warning + +# CA2244: Do not duplicate indexed element initializations +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2244 +dotnet_diagnostic.CA2244.severity = suggestion + +# CA2245: Do not assign a property to itself +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2245 +dotnet_diagnostic.CA2245.severity = suggestion + +# CA2246: Assigning symbol and its member in the same statement +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2246 +dotnet_diagnostic.CA2246.severity = suggestion + +# CA2247: Argument passed to TaskCompletionSource constructor should be TaskCreationOptions enum instead of TaskContinuationOptions enum +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2247 +dotnet_diagnostic.CA2247.severity = warning + +# CA2248: Provide correct 'enum' argument to 'Enum.HasFlag' +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2248 +dotnet_diagnostic.CA2248.severity = suggestion + +# CA2249: Consider using 'string.Contains' instead of 'string.IndexOf' +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2249 +dotnet_diagnostic.CA2249.severity = warning + +# CA2250: Use 'ThrowIfCancellationRequested' +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2250 +dotnet_diagnostic.CA2250.severity = warning + +# CA2251: Use 'string.Equals' +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2251 +dotnet_diagnostic.CA2251.severity = warning + +# CA2252: This API requires opting into preview features +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2252 +dotnet_diagnostic.CA2251.severity = none + +# CA2300: Do not use insecure deserializer BinaryFormatter +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2300 +dotnet_diagnostic.CA2300.severity = none + +# CA2301: Do not call BinaryFormatter.Deserialize without first setting BinaryFormatter.Binder +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2301 +dotnet_diagnostic.CA2301.severity = none + +# CA2302: Ensure BinaryFormatter.Binder is set before calling BinaryFormatter.Deserialize +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2302 +dotnet_diagnostic.CA2302.severity = none + +# CA2305: Do not use insecure deserializer LosFormatter +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2305 +dotnet_diagnostic.CA2305.severity = none + +# CA2310: Do not use insecure deserializer NetDataContractSerializer +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2310 +dotnet_diagnostic.CA2310.severity = none + +# CA2311: Do not deserialize without first setting NetDataContractSerializer.Binder +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2311 +dotnet_diagnostic.CA2311.severity = none + +# CA2312: Ensure NetDataContractSerializer.Binder is set before deserializing +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2312 +dotnet_diagnostic.CA2312.severity = none + +# CA2315: Do not use insecure deserializer ObjectStateFormatter +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2315 +dotnet_diagnostic.CA2315.severity = none + +# CA2321: Do not deserialize with JavaScriptSerializer using a SimpleTypeResolver +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2321 +dotnet_diagnostic.CA2321.severity = none + +# CA2322: Ensure JavaScriptSerializer is not initialized with SimpleTypeResolver before deserializing +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2322 +dotnet_diagnostic.CA2322.severity = none + +# CA2326: Do not use TypeNameHandling values other than None +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2326 +dotnet_diagnostic.CA2326.severity = none + +# CA2327: Do not use insecure JsonSerializerSettings +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2327 +dotnet_diagnostic.CA2327.severity = none + +# CA2328: Ensure that JsonSerializerSettings are secure +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2328 +dotnet_diagnostic.CA2328.severity = none + +# CA2329: Do not deserialize with JsonSerializer using an insecure configuration +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2329 +dotnet_diagnostic.CA2329.severity = none + +# CA2330: Ensure that JsonSerializer has a secure configuration when deserializing +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2330 +dotnet_diagnostic.CA2330.severity = none + +# CA2350: Do not use DataTable.ReadXml() with untrusted data +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2350 +dotnet_diagnostic.CA2350.severity = none + +# CA2351: Do not use DataSet.ReadXml() with untrusted data +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2351 +dotnet_diagnostic.CA2351.severity = none + +# CA2352: Unsafe DataSet or DataTable in serializable type can be vulnerable to remote code execution attacks +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2352 +dotnet_diagnostic.CA2352.severity = none + +# CA2353: Unsafe DataSet or DataTable in serializable type +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2353 +dotnet_diagnostic.CA2353.severity = none + +# CA2354: Unsafe DataSet or DataTable in deserialized object graph can be vulnerable to remote code execution attacks +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2354 +dotnet_diagnostic.CA2354.severity = none + +# CA2355: Unsafe DataSet or DataTable type found in deserializable object graph +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2355 +dotnet_diagnostic.CA2355.severity = none + +# CA2356: Unsafe DataSet or DataTable type in web deserializable object graph +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2356 +dotnet_diagnostic.CA2356.severity = none + +# CA2361: Ensure autogenerated class containing DataSet.ReadXml() is not used with untrusted data +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2361 +dotnet_diagnostic.CA2361.severity = none + +# CA2362: Unsafe DataSet or DataTable in autogenerated serializable type can be vulnerable to remote code execution attacks +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2362 +dotnet_diagnostic.CA2362.severity = none + +# CA3001: Review code for SQL injection vulnerabilities +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca3001 +dotnet_diagnostic.CA3001.severity = none + +# CA3002: Review code for XSS vulnerabilities +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca3002 +dotnet_diagnostic.CA3002.severity = none + +# CA3003: Review code for file path injection vulnerabilities +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca3003 +dotnet_diagnostic.CA3003.severity = none + +# CA3004: Review code for information disclosure vulnerabilities +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca3004 +dotnet_diagnostic.CA3004.severity = none + +# CA3005: Review code for LDAP injection vulnerabilities +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca3005 +dotnet_diagnostic.CA3005.severity = none + +# CA3006: Review code for process command injection vulnerabilities +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca3006 +dotnet_diagnostic.CA3006.severity = none + +# CA3007: Review code for open redirect vulnerabilities +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca3007 +dotnet_diagnostic.CA3007.severity = none + +# CA3008: Review code for XPath injection vulnerabilities +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca3008 +dotnet_diagnostic.CA3008.severity = none + +# CA3009: Review code for XML injection vulnerabilities +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca3009 +dotnet_diagnostic.CA3009.severity = none + +# CA3010: Review code for XAML injection vulnerabilities +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca3010 +dotnet_diagnostic.CA3010.severity = none + +# CA3011: Review code for DLL injection vulnerabilities +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca3011 +dotnet_diagnostic.CA3011.severity = none + +# CA3012: Review code for regex injection vulnerabilities +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca3012 +dotnet_diagnostic.CA3012.severity = none + +# CA3061: Do Not Add Schema By URL +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca3061 +dotnet_diagnostic.CA3061.severity = silent + +# CA3075: Insecure DTD processing in XML +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca3075 +dotnet_diagnostic.CA3075.severity = silent + +# CA3076: Insecure XSLT script processing. +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca3076 +dotnet_diagnostic.CA3076.severity = silent + +# CA3077: Insecure Processing in API Design, XmlDocument and XmlTextReader +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca3077 +dotnet_diagnostic.CA3077.severity = silent + +# CA3147: Mark Verb Handlers With Validate Antiforgery Token +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca3147 +dotnet_diagnostic.CA3147.severity = silent + +# CA5350: Do Not Use Weak Cryptographic Algorithms +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5350 +dotnet_diagnostic.CA5350.severity = silent + +# CA5351: Do Not Use Broken Cryptographic Algorithms +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5351 +dotnet_diagnostic.CA5351.severity = silent + +# CA5358: Review cipher mode usage with cryptography experts +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5358 +dotnet_diagnostic.CA5358.severity = none + +# CA5359: Do Not Disable Certificate Validation +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5359 +dotnet_diagnostic.CA5359.severity = silent + +# CA5360: Do Not Call Dangerous Methods In Deserialization +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5360 +dotnet_diagnostic.CA5360.severity = silent + +# CA5361: Do Not Disable SChannel Use of Strong Crypto +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5361 +dotnet_diagnostic.CA5361.severity = none + +# CA5362: Potential reference cycle in deserialized object graph +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5362 +dotnet_diagnostic.CA5362.severity = none + +# CA5363: Do Not Disable Request Validation +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5363 +dotnet_diagnostic.CA5363.severity = silent + +# CA5364: Do Not Use Deprecated Security Protocols +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5364 +dotnet_diagnostic.CA5364.severity = silent + +# CA5365: Do Not Disable HTTP Header Checking +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5365 +dotnet_diagnostic.CA5365.severity = silent + +# CA5366: Use XmlReader For DataSet Read Xml +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5366 +dotnet_diagnostic.CA5366.severity = silent + +# CA5367: Do Not Serialize Types With Pointer Fields +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5367 +dotnet_diagnostic.CA5367.severity = none + +# CA5368: Set ViewStateUserKey For Classes Derived From Page +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5368 +dotnet_diagnostic.CA5368.severity = silent + +# CA5369: Use XmlReader For Deserialize +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5369 +dotnet_diagnostic.CA5369.severity = silent + +# CA5370: Use XmlReader For Validating Reader +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5370 +dotnet_diagnostic.CA5370.severity = silent + +# CA5371: Use XmlReader For Schema Read +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5371 +dotnet_diagnostic.CA5371.severity = silent + +# CA5372: Use XmlReader For XPathDocument +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5372 +dotnet_diagnostic.CA5372.severity = silent + +# CA5373: Do not use obsolete key derivation function +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5373 +dotnet_diagnostic.CA5373.severity = silent + +# CA5374: Do Not Use XslTransform +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5374 +dotnet_diagnostic.CA5374.severity = silent + +# CA5375: Do Not Use Account Shared Access Signature +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5375 +dotnet_diagnostic.CA5375.severity = none + +# CA5376: Use SharedAccessProtocol HttpsOnly +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5376 +dotnet_diagnostic.CA5376.severity = none + +# CA5377: Use Container Level Access Policy +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5377 +dotnet_diagnostic.CA5377.severity = none + +# CA5378: Do not disable ServicePointManagerSecurityProtocols +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5378 +dotnet_diagnostic.CA5378.severity = none + +# CA5379: Do Not Use Weak Key Derivation Function Algorithm +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5379 +dotnet_diagnostic.CA5379.severity = silent + +# CA5380: Do Not Add Certificates To Root Store +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5380 +dotnet_diagnostic.CA5380.severity = none + +# CA5381: Ensure Certificates Are Not Added To Root Store +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5381 +dotnet_diagnostic.CA5381.severity = none + +# CA5382: Use Secure Cookies In ASP.Net Core +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5382 +dotnet_diagnostic.CA5382.severity = none + +# CA5383: Ensure Use Secure Cookies In ASP.Net Core +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5383 +dotnet_diagnostic.CA5383.severity = none + +# CA5384: Do Not Use Digital Signature Algorithm (DSA) +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5384 +dotnet_diagnostic.CA5384.severity = silent + +# CA5385: Use Rivest–Shamir–Adleman (RSA) Algorithm With Sufficient Key Size +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5385 +dotnet_diagnostic.CA5385.severity = silent + +# CA5386: Avoid hardcoding SecurityProtocolType value +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5386 +dotnet_diagnostic.CA5386.severity = none + +# CA5387: Do Not Use Weak Key Derivation Function With Insufficient Iteration Count +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5387 +dotnet_diagnostic.CA5387.severity = none + +# CA5388: Ensure Sufficient Iteration Count When Using Weak Key Derivation Function +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5388 +dotnet_diagnostic.CA5388.severity = none + +# CA5389: Do Not Add Archive Item's Path To The Target File System Path +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5389 +dotnet_diagnostic.CA5389.severity = none + +# CA5390: Do not hard-code encryption key +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5390 +dotnet_diagnostic.CA5390.severity = none + +# CA5391: Use antiforgery tokens in ASP.NET Core MVC controllers +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5391 +dotnet_diagnostic.CA5391.severity = none + +# CA5392: Use DefaultDllImportSearchPaths attribute for P/Invokes +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5392 +dotnet_diagnostic.CA5392.severity = none + +# CA5393: Do not use unsafe DllImportSearchPath value +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5393 +dotnet_diagnostic.CA5393.severity = none + +# CA5394: Do not use insecure randomness +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5394 +dotnet_diagnostic.CA5394.severity = none + +# CA5395: Miss HttpVerb attribute for action methods +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5395 +dotnet_diagnostic.CA5395.severity = none + +# CA5396: Set HttpOnly to true for HttpCookie +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5396 +dotnet_diagnostic.CA5396.severity = none + +# CA5397: Do not use deprecated SslProtocols values +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5397 +dotnet_diagnostic.CA5397.severity = silent + +# CA5398: Avoid hardcoded SslProtocols values +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5398 +dotnet_diagnostic.CA5398.severity = none + +# CA5399: HttpClients should enable certificate revocation list checks +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5399 +dotnet_diagnostic.CA5399.severity = none + +# CA5400: Ensure HttpClient certificate revocation list check is not disabled +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5400 +dotnet_diagnostic.CA5400.severity = none + +# CA5401: Do not use CreateEncryptor with non-default IV +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5401 +dotnet_diagnostic.CA5401.severity = none + +# CA5402: Use CreateEncryptor with the default IV +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5402 +dotnet_diagnostic.CA5402.severity = none + +# CA5403: Do not hard-code certificate +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5403 +dotnet_diagnostic.CA5403.severity = none + +# IL3000: Avoid using accessing Assembly file path when publishing as a single-file +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/il3000 +dotnet_diagnostic.IL3000.severity = warning + +# IL3001: Avoid using accessing Assembly file path when publishing as a single-file +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/il3001 +dotnet_diagnostic.IL3001.severity = warning + +# IL3002: Using member with RequiresAssemblyFilesAttribute can break functionality when embedded in a single-file app +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/il3002 +dotnet_diagnostic.IL3002.severity = warning + +# DOC100: PlaceTextInParagraphs +# https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC100.md +dotnet_diagnostic.DOC100.severity = none + +# DOC101: UseChildBlocksConsistently +# https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC101.md +dotnet_diagnostic.DOC101.severity = none + +# DOC102: UseChildBlocksConsistentlyAcrossElementsOfTheSameKind +# https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC102.md +dotnet_diagnostic.DOC102.severity = none + +# DOC103: UseUnicodeCharacters +# https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC103.md +dotnet_diagnostic.DOC103.severity = none + +# DOC104: UseSeeLangword +# https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC104.md +dotnet_diagnostic.DOC104.severity = suggestion + +# DOC105: UseParamref +# https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC105.md +dotnet_diagnostic.DOC105.severity = none + +# DOC106: UseTypeparamref +# https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC106.md +dotnet_diagnostic.DOC106.severity = none + +# DOC107: UseSeeCref +# https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC107.md +dotnet_diagnostic.DOC107.severity = none + +# DOC108: AvoidEmptyParagraphs +# https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC108.md +dotnet_diagnostic.DOC108.severity = none + +# DOC200: UseXmlDocumentationSyntax +# https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC200.md +dotnet_diagnostic.DOC200.severity = none + +# DOC201: ItemShouldHaveDescription +# https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC201.md +dotnet_diagnostic.DOC201.severity = none + +# DOC202: UseSectionElementsCorrectly +# https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC202.md +dotnet_diagnostic.DOC202.severity = none + +# DOC203: UseBlockElementsCorrectly +# https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC203.md +dotnet_diagnostic.DOC203.severity = none + +# DOC204: UseInlineElementsCorrectly +# https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC204.md +dotnet_diagnostic.DOC204.severity = none + +# DOC207: UseSeeLangwordCorrectly +# https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC207.md +dotnet_diagnostic.DOC207.severity = none + +# DOC209: UseSeeHrefCorrectly +# https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC209.md +dotnet_diagnostic.DOC209.severity = none + +# IDE0001: SimplifyNames +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0001 +dotnet_diagnostic.IDE0001.severity = silent + +# IDE0002: SimplifyMemberAccess +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0002 +dotnet_diagnostic.IDE0002.severity = silent + +# IDE0003: RemoveQualification +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0003 +dotnet_diagnostic.IDE0003.severity = silent + +# IDE0004: RemoveUnnecessaryCast +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0004 +dotnet_diagnostic.IDE0004.severity = silent + +# IDE0005: RemoveUnnecessaryImports +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0005 +dotnet_diagnostic.IDE0005.severity = silent + +# IDE0006: IntellisenseBuildFailed +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0006 +dotnet_diagnostic.IDE0006.severity = silent + +# IDE0007: UseImplicitType +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0007 +dotnet_diagnostic.IDE0007.severity = silent + +# IDE0008: UseExplicitType +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008 +dotnet_diagnostic.IDE0008.severity = silent + +# IDE0009: AddQualification +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0009 +dotnet_diagnostic.IDE0009.severity = silent + +# IDE0010: PopulateSwitchStatement +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0010 +dotnet_diagnostic.IDE0010.severity = silent + +# IDE0011: AddBraces +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0011 +dotnet_diagnostic.IDE0011.severity = silent + +# IDE0016: UseThrowExpression +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0016 +dotnet_diagnostic.IDE0016.severity = silent + +# IDE0017: UseObjectInitializer +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0017 +dotnet_diagnostic.IDE0017.severity = silent + +# IDE0018: InlineDeclaration +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0018 +dotnet_diagnostic.IDE0018.severity = silent + +# IDE0019: InlineAsTypeCheck +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0019 +dotnet_diagnostic.IDE0019.severity = silent + +# IDE0020: InlineIsTypeCheck +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0020 +dotnet_diagnostic.IDE0020.severity = silent + +# IDE0021: UseExpressionBodyForConstructors +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0021 +dotnet_diagnostic.IDE0021.severity = silent + +# IDE0022: UseExpressionBodyForMethods +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0022 +dotnet_diagnostic.IDE0022.severity = silent + +# IDE0023: UseExpressionBodyForConversionOperators +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0023 +dotnet_diagnostic.IDE0023.severity = silent + +# IDE0024: UseExpressionBodyForOperators +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0024 +dotnet_diagnostic.IDE0024.severity = silent + +# IDE0025: UseExpressionBodyForProperties +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0025 +dotnet_diagnostic.IDE0025.severity = silent + +# IDE0026: UseExpressionBodyForIndexers +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0026 +dotnet_diagnostic.IDE0026.severity = silent + +# IDE0027: UseExpressionBodyForAccessors +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0027 +dotnet_diagnostic.IDE0027.severity = silent + +# IDE0028: UseCollectionInitializer +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0028 +dotnet_diagnostic.IDE0028.severity = silent + +# IDE0029: UseCoalesceExpression +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0029 +dotnet_diagnostic.IDE0029.severity = warning + +# IDE0030: UseCoalesceExpressionForNullable +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0030 +dotnet_diagnostic.IDE0030.severity = warning + +# IDE0031: UseNullPropagation +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0031 +dotnet_diagnostic.IDE0031.severity = warning + +# IDE0032: UseAutoProperty +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0032 +dotnet_diagnostic.IDE0032.severity = silent + +# IDE0033: UseExplicitTupleName +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0033 +dotnet_diagnostic.IDE0033.severity = silent + +# IDE0034: UseDefaultLiteral +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0034 +dotnet_diagnostic.IDE0034.severity = silent + +# IDE0035: RemoveUnreachableCode +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0035 +dotnet_diagnostic.IDE0035.severity = silent + +# IDE0036: OrderModifiers +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0036 +dotnet_diagnostic.IDE0036.severity = warning + +# IDE0037: UseInferredMemberName +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0037 +dotnet_diagnostic.IDE0037.severity = silent + +# IDE0038: InlineIsTypeWithoutNameCheck +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0038 +dotnet_diagnostic.IDE0038.severity = silent + +# IDE0039: UseLocalFunction +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0039 +dotnet_diagnostic.IDE0039.severity = silent + +# IDE0040: AddAccessibilityModifiers +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0040 +dotnet_diagnostic.IDE0040.severity = warning + +# IDE0041: UseIsNullCheck +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0041 +dotnet_diagnostic.IDE0041.severity = warning + +# IDE0042: UseDeconstruction +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0042 +dotnet_diagnostic.IDE0042.severity = silent + +# IDE0043: ValidateFormatString +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0043 +dotnet_diagnostic.IDE0043.severity = silent + +# IDE0044: MakeFieldReadonly +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0044 +dotnet_diagnostic.IDE0044.severity = warning + +# IDE0045: UseConditionalExpressionForAssignment +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0045 +dotnet_diagnostic.IDE0045.severity = silent + +# IDE0046: UseConditionalExpressionForReturn +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0046 +dotnet_diagnostic.IDE0046.severity = silent + +# IDE0047: RemoveUnnecessaryParentheses +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0047 +dotnet_diagnostic.IDE0047.severity = silent + +# IDE0048: AddRequiredParentheses +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0048 +dotnet_diagnostic.IDE0048.severity = suggestion + +# IDE0049: PreferBuiltInOrFrameworkType +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0049 +dotnet_diagnostic.IDE0049.severity = warning + +# IDE0050: ConvertAnonymousTypeToTuple +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0050 +dotnet_diagnostic.IDE0050.severity = silent + +# IDE0051: RemoveUnusedMembers +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0051 +dotnet_diagnostic.IDE0051.severity = silent + +# IDE0052: RemoveUnreadMembers +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0052 +dotnet_diagnostic.IDE0052.severity = silent + +# IDE0053: UseExpressionBodyForLambdaExpressions +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0053 +dotnet_diagnostic.IDE0053.severity = silent + +# IDE0054: UseCompoundAssignment +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0054 +dotnet_diagnostic.IDE0054.severity = warning + +# IDE0055: Formatting +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0055 +dotnet_diagnostic.IDE0055.severity = silent + +# IDE0056: UseIndexOperator +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0056 +dotnet_diagnostic.IDE0056.severity = silent + +# IDE0057: UseRangeOperator +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0057 +dotnet_diagnostic.IDE0057.severity = silent + +# IDE0058: ExpressionValueIsUnused +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0058 +dotnet_diagnostic.IDE0058.severity = silent + +# IDE0059: ValueAssignedIsUnused +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0059 +dotnet_diagnostic.IDE0059.severity = silent + +# IDE0060: UnusedParameter +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0060 +dotnet_diagnostic.IDE0060.severity = silent + +# IDE0061: UseExpressionBodyForLocalFunctions +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0061 +dotnet_diagnostic.IDE0061.severity = silent + +# IDE0062: MakeLocalFunctionStatic +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0062 +dotnet_diagnostic.IDE0062.severity = warning + +# IDE0063: UseSimpleUsingStatement +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0063 +dotnet_diagnostic.IDE0063.severity = silent + +# IDE0064: MakeStructFieldsWritable +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0064 +dotnet_diagnostic.IDE0064.severity = warning + +# IDE0065: MoveMisplacedUsingDirectives +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0065 +dotnet_diagnostic.IDE0065.severity = silent + +# IDE0066: ConvertSwitchStatementToExpression +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0066 +dotnet_diagnostic.IDE0066.severity = silent + +# IDE0070: UseSystemHashCode +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0070 +dotnet_diagnostic.IDE0070.severity = warning + +# IDE0071: SimplifyInterpolation +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0071 +dotnet_diagnostic.IDE0071.severity = silent + +# IDE0072: PopulateSwitchExpression +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0072 +dotnet_diagnostic.IDE0072.severity = silent + +# IDE0073: FileHeaderMismatch +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0073 +dotnet_diagnostic.IDE0073.severity = suggestion + +# IDE0074: UseCoalesceCompoundAssignment +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0074 +dotnet_diagnostic.IDE0074.severity = warning + +# IDE0075: SimplifyConditionalExpression +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0075 +dotnet_diagnostic.IDE0075.severity = warning + +# IDE0076: InvalidSuppressMessageAttribute +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0076 +dotnet_diagnostic.IDE0076.severity = warning + +# IDE0077: LegacyFormatSuppressMessageAttribute +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0077 +dotnet_diagnostic.IDE0077.severity = warning + +# IDE0078: UsePatternCombinators +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0078 +dotnet_diagnostic.IDE0078.severity = silent + +# IDE0079: RemoveUnnecessarySuppression +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0079 +dotnet_diagnostic.IDE0079.severity = silent + +# IDE0080: RemoveConfusingSuppressionForIsExpression +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0080 +dotnet_diagnostic.IDE0080.severity = silent + +# IDE0081: RemoveUnnecessaryByVal +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0081 +dotnet_diagnostic.IDE0081.severity = silent + +# IDE0082: ConvertTypeOfToNameOf +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0082 +dotnet_diagnostic.IDE0082.severity = warning + +# IDE0083: UseNotPattern +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0083 +dotnet_diagnostic.IDE0083.severity = silent + +# IDE0084: UseIsNotExpression +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0084 +dotnet_diagnostic.IDE0084.severity = silent + +# IDE0090: UseNew +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0090 +dotnet_diagnostic.IDE0090.severity = suggestion + +# IDE0100: RemoveRedundantEquality +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0100 +dotnet_diagnostic.IDE0100.severity = warning + +# IDE0110: RemoveUnnecessaryDiscard +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0110 +dotnet_diagnostic.IDE0110.severity = suggestion + +# IDE0120: SimplifyLINQExpression +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0120 +dotnet_diagnostic.IDE0120.severity = warning + +# IDE0130: NamespaceDoesNotMatchFolderStructure +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0130 +dotnet_diagnostic.IDE0130.severity = silent + +# IDE1001: AnalyzerChanged +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide1001 +dotnet_diagnostic.IDE1001.severity = silent + +# IDE1002: AnalyzerDependencyConflict +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide1002 +dotnet_diagnostic.IDE1002.severity = silent + +# IDE1003: MissingAnalyzerReference +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide1003 +dotnet_diagnostic.IDE1003.severity = silent + +# IDE1004: ErrorReadingRuleset +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide1004 +dotnet_diagnostic.IDE1004.severity = silent + +# IDE1005: InvokeDelegateWithConditionalAccess +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide1005 +dotnet_diagnostic.IDE1005.severity = warning + +# IDE1006: NamingRule +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide1006 +dotnet_diagnostic.IDE1006.severity = silent + +# IDE1007: UnboundIdentifier +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide1007 +dotnet_diagnostic.IDE1007.severity = silent + +# IDE1008: UnboundConstructor +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide1008 +dotnet_diagnostic.IDE1008.severity = silent + +# IDE2000: MultipleBlankLines +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2000 +dotnet_diagnostic.IDE2000.severity = warning + +# IDE2001: EmbeddedStatementsMustBeOnTheirOwnLine +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2001 +dotnet_diagnostic.IDE2001.severity = warning + +# IDE2002: ConsecutiveBracesMustNotHaveBlankLinesBetweenThem +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2002 +dotnet_diagnostic.IDE2002.severity = warning + +# IDE2003: ConsecutiveStatementPlacement +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2003 +dotnet_diagnostic.IDE2003.severity = warning + +# IDE2004: BlankLineNotAllowedAfterConstructorInitializerColon +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2004 +dotnet_diagnostic.IDE2004.severity = warning + +# SA0001: XML comment analysis disabled +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA0001.md +dotnet_diagnostic.SA0001.severity = none + +# SA0002: Invalid settings file +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA0002.md +dotnet_diagnostic.SA0002.severity = none + +# SA1000: Keywords should be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1000.md +dotnet_diagnostic.SA1000.severity = warning + +# SA1001: Commas should be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1001.md +dotnet_diagnostic.SA1001.severity = warning + +# SA1002: Semicolons should be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1002.md +dotnet_diagnostic.SA1002.severity = warning + +# SA1003: Symbols should be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1003.md +dotnet_diagnostic.SA1003.severity = warning + +# SA1004: Documentation lines should begin with single space +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1004.md +dotnet_diagnostic.SA1004.severity = none + +# SA1005: Single line comments should begin with single space +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1005.md +dotnet_diagnostic.SA1005.severity = none + +# SA1006: Preprocessor keywords should not be preceded by space +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1006.md +dotnet_diagnostic.SA1006.severity = warning + +# SA1007: Operator keyword should be followed by space +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1007.md +dotnet_diagnostic.SA1007.severity = warning + +# SA1008: Opening parenthesis should be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1008.md +dotnet_diagnostic.SA1008.severity = warning + +# SA1009: Closing parenthesis should be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1009.md +dotnet_diagnostic.SA1009.severity = none + +# SA1010: Opening square brackets should be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1010.md +dotnet_diagnostic.SA1010.severity = none + +# SA1011: Closing square brackets should be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1011.md +dotnet_diagnostic.SA1011.severity = none + +# SA1012: Opening braces should be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1012.md +dotnet_diagnostic.SA1012.severity = none + +# SA1013: Closing braces should be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1013.md +dotnet_diagnostic.SA1013.severity = none + +# SA1014: Opening generic brackets should be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1014.md +dotnet_diagnostic.SA1014.severity = none + +# SA1015: Closing generic brackets should be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1015.md +dotnet_diagnostic.SA1015.severity = none + +# SA1016: Opening attribute brackets should be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1016.md +dotnet_diagnostic.SA1016.severity = none + +# SA1017: Closing attribute brackets should be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1017.md +dotnet_diagnostic.SA1017.severity = none + +# SA1018: Nullable type symbols should be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1018.md +dotnet_diagnostic.SA1018.severity = none + +# SA1019: Member access symbols should be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1019.md +dotnet_diagnostic.SA1019.severity = none + +# SA1020: Increment decrement symbols should be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1020.md +dotnet_diagnostic.SA1020.severity = none + +# SA1021: Negative signs should be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1021.md +dotnet_diagnostic.SA1021.severity = none + +# SA1022: Positive signs should be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1022.md +dotnet_diagnostic.SA1022.severity = none + +# SA1023: Dereference and access of symbols should be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1023.md +dotnet_diagnostic.SA1023.severity = none + +# SA1024: Colons Should Be Spaced Correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1024.md +dotnet_diagnostic.SA1024.severity = none + +# SA1025: Code should not contain multiple whitespace in a row +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1025.md +dotnet_diagnostic.SA1025.severity = none + +# SA1026: Code should not contain space after new or stackalloc keyword in implicitly typed array allocation +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1026.md +dotnet_diagnostic.SA1026.severity = none + +# SA1027: Use tabs correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1027.md +dotnet_diagnostic.SA1027.severity = none + +# SA1028: Code should not contain trailing whitespace +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1028.md +dotnet_diagnostic.SA1028.severity = none + +# SA1100: Do not prefix calls with base unless local implementation exists +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1100.md +dotnet_diagnostic.SA1100.severity = none + +# SA1101: Prefix local calls with this +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1101.md +dotnet_diagnostic.SA1101.severity = none + +# SA1102: Query clause should follow previous clause +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1102.md +dotnet_diagnostic.SA1102.severity = none + +# SA1103: Query clauses should be on separate lines or all on one line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1103.md +dotnet_diagnostic.SA1103.severity = none + +# SA1104: Query clause should begin on new line when previous clause spans multiple lines +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1104.md +dotnet_diagnostic.SA1104.severity = none + +# SA1105: Query clauses spanning multiple lines should begin on own line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1105.md +dotnet_diagnostic.SA1105.severity = none + +# SA1106: Code should not contain empty statements +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1106.md +dotnet_diagnostic.SA1106.severity = warning + +# SA1107: Code should not contain multiple statements on one line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1107.md +dotnet_diagnostic.SA1107.severity = none + +# SA1108: Block statements should not contain embedded comments +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1108.md +dotnet_diagnostic.SA1108.severity = none + +# SA1110: Opening parenthesis or bracket should be on declaration line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1110.md +dotnet_diagnostic.SA1110.severity = none + +# SA1111: Closing parenthesis should be on line of last parameter +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1111.md +dotnet_diagnostic.SA1111.severity = none + +# SA1112: Closing parenthesis should be on line of opening parenthesis +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1112.md +dotnet_diagnostic.SA1112.severity = none + +# SA1113: Comma should be on the same line as previous parameter +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1113.md +dotnet_diagnostic.SA1113.severity = none + +# SA1114: Parameter list should follow declaration +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1114.md +dotnet_diagnostic.SA1114.severity = none + +# SA1115: Parameter should follow comma +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1115.md +dotnet_diagnostic.SA1115.severity = none + +# SA1116: Split parameters should start on line after declaration +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1116.md +dotnet_diagnostic.SA1116.severity = none + +# SA1117: Parameters should be on same line or separate lines +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1117.md +dotnet_diagnostic.SA1117.severity = none + +# SA1118: Parameter should not span multiple lines +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1118.md +dotnet_diagnostic.SA1118.severity = none + +# SA1119: Statement should not use unnecessary parenthesis +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1119.md +dotnet_diagnostic.SA1119.severity = none + +# SA1120: Comments should contain text +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1120.md +dotnet_diagnostic.SA1120.severity = none + +# SA1121: Use built-in type alias +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1121.md +dotnet_diagnostic.SA1121.severity = none + +# SA1122: Use string.Empty for empty strings +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1122.md +dotnet_diagnostic.SA1122.severity = warning + +# SA1123: Do not place regions within elements +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1123.md +dotnet_diagnostic.SA1123.severity = none + +# SA1124: Do not use regions +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1124.md +dotnet_diagnostic.SA1124.severity = none + +# SA1125: Use shorthand for nullable types +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1125.md +dotnet_diagnostic.SA1125.severity = none + +# SA1127: Generic type constraints should be on their own line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1127.md +dotnet_diagnostic.SA1127.severity = none + +# SA1128: Put constructor initializers on their own line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1128.md +dotnet_diagnostic.SA1128.severity = none + +# SA1129: Do not use default value type constructor +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1129.md +dotnet_diagnostic.SA1129.severity = none + +# SA1130: Use lambda syntax +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1130.md +dotnet_diagnostic.SA1130.severity = none + +# SA1131: Use readable conditions +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1131.md +dotnet_diagnostic.SA1131.severity = warning + +# SA1132: Do not combine fields +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1132.md +dotnet_diagnostic.SA1132.severity = none + +# SA1133: Do not combine attributes +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1133.md +dotnet_diagnostic.SA1133.severity = none + +# SA1134: Attributes should not share line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1134.md +dotnet_diagnostic.SA1134.severity = none + +# SA1135: Using directives should be qualified +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1135.md +dotnet_diagnostic.SA1135.severity = none + +# SA1136: Enum values should be on separate lines +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1136.md +dotnet_diagnostic.SA1136.severity = none + +# SA1137: Elements should have the same indentation +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1137.md +dotnet_diagnostic.SA1137.severity = none + +# SA1139: Use literal suffix notation instead of casting +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1139.md +dotnet_diagnostic.SA1139.severity = none + +# SA1141: Use tuple syntax +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1141.md +dotnet_diagnostic.SA1141.severity = none + +# SA1142: Refer to tuple fields by name +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1142.md +dotnet_diagnostic.SA1142.severity = none + +# SA1200: Using directives should be placed correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1200.md +dotnet_diagnostic.SA1200.severity = none + +# SA1201: Elements should appear in the correct order +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1201.md +dotnet_diagnostic.SA1201.severity = none + +# SA1202: Elements should be ordered by access +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1202.md +dotnet_diagnostic.SA1202.severity = none + +# SA1203: Constants should appear before fields +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1203.md +dotnet_diagnostic.SA1203.severity = none + +# SA1204: Static elements should appear before instance elements +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1204.md +dotnet_diagnostic.SA1204.severity = none + +# SA1205: Partial elements should declare access +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1205.md +dotnet_diagnostic.SA1205.severity = warning + +# SA1206: Declaration keywords should follow order +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1206.md +dotnet_diagnostic.SA1206.severity = none + +# SA1207: Protected should come before internal +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1207.md +dotnet_diagnostic.SA1207.severity = none + +# SA1208: System using directives should be placed before other using directives +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1208.md +dotnet_diagnostic.SA1208.severity = none + +# SA1209: Using alias directives should be placed after other using directives +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1209.md +dotnet_diagnostic.SA1209.severity = none + +# SA1210: Using directives should be ordered alphabetically by namespace +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1210.md +dotnet_diagnostic.SA1210.severity = none + +# SA1211: Using alias directives should be ordered alphabetically by alias name +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1211.md +dotnet_diagnostic.SA1211.severity = none + +# SA1212: Property accessors should follow order +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1212.md +dotnet_diagnostic.SA1212.severity = warning + +# SA1213: Event accessors should follow order +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1213.md +dotnet_diagnostic.SA1213.severity = warning + +# SA1214: Readonly fields should appear before non-readonly fields +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1214.md +dotnet_diagnostic.SA1214.severity = none + +# SA1216: Using static directives should be placed at the correct location +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1216.md +dotnet_diagnostic.SA1216.severity = warning + +# SA1217: Using static directives should be ordered alphabetically +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1217.md +dotnet_diagnostic.SA1217.severity = warning + +# SA1300: Element should begin with upper-case letter +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1300.md +dotnet_diagnostic.SA1300.severity = none + +# SA1302: Interface names should begin with I +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1302.md +dotnet_diagnostic.SA1302.severity = none + +# SA1303: Const field names should begin with upper-case letter +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1303.md +dotnet_diagnostic.SA1303.severity = none + +# SA1304: Non-private readonly fields should begin with upper-case letter +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1304.md +dotnet_diagnostic.SA1304.severity = none + +# SA1305: Field names should not use Hungarian notation +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1305.md +dotnet_diagnostic.SA1305.severity = none + +# SA1306: Field names should begin with lower-case letter +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1306.md +dotnet_diagnostic.SA1306.severity = none + +# SA1307: Accessible fields should begin with upper-case letter +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1307.md +dotnet_diagnostic.SA1307.severity = none + +# SA1308: Variable names should not be prefixed +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1308.md +dotnet_diagnostic.SA1308.severity = none + +# SA1309: Field names should not begin with underscore +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1309.md +dotnet_diagnostic.SA1309.severity = none + +# SA1310: Field names should not contain underscore +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1310.md +dotnet_diagnostic.SA1310.severity = none + +# SA1311: Static readonly fields should begin with upper-case letter +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1311.md +dotnet_diagnostic.SA1311.severity = none + +# SA1312: Variable names should begin with lower-case letter +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1312.md +dotnet_diagnostic.SA1312.severity = none + +# SA1313: Parameter names should begin with lower-case letter +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1313.md +dotnet_diagnostic.SA1313.severity = none + +# SA1314: Type parameter names should begin with T +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1314.md +dotnet_diagnostic.SA1314.severity = warning + +# SA1316: Tuple element names should use correct casing +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1316.md +dotnet_diagnostic.SA1316.severity = none + +# SA1400: Access modifier should be declared +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1400.md +dotnet_diagnostic.SA1400.severity = none + +# SA1401: Fields should be private +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1401.md +dotnet_diagnostic.SA1401.severity = none + +# SA1402: File may only contain a single type +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1402.md +dotnet_diagnostic.SA1402.severity = none + +# SA1403: File may only contain a single namespace +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1403.md +dotnet_diagnostic.SA1403.severity = none + +# SA1404: Code analysis suppression should have justification +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1404.md +dotnet_diagnostic.SA1404.severity = none + +# SA1405: Debug.Assert should provide message text +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1405.md +dotnet_diagnostic.SA1405.severity = none + +# SA1406: Debug.Fail should provide message text +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1406.md +dotnet_diagnostic.SA1406.severity = none + +# SA1407: Arithmetic expressions should declare precedence +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1407.md +dotnet_diagnostic.SA1407.severity = none + +# SA1408: Conditional expressions should declare precedence +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1408.md +dotnet_diagnostic.SA1408.severity = none + +# SA1410: Remove delegate parenthesis when possible +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1410.md +dotnet_diagnostic.SA1410.severity = none + +# SA1411: Attribute constructor should not use unnecessary parenthesis +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1411.md +dotnet_diagnostic.SA1411.severity = none + +# SA1412: Store files as UTF-8 with byte order mark +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1412.md +dotnet_diagnostic.SA1412.severity = none + +# SA1413: Use trailing comma in multi-line initializers +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1413.md +dotnet_diagnostic.SA1413.severity = none + +# SA1414: Tuple types in signatures should have element names +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1414.md +dotnet_diagnostic.SA1414.severity = none + +# SA1500: Braces for multi-line statements should not share line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1500.md +dotnet_diagnostic.SA1500.severity = none + +# SA1501: Statement should not be on a single line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1501.md +dotnet_diagnostic.SA1501.severity = none + +# SA1502: Element should not be on a single line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1502.md +dotnet_diagnostic.SA1502.severity = none + +# SA1503: Braces should not be omitted +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1503.md +dotnet_diagnostic.SA1503.severity = none + +# SA1504: All accessors should be single-line or multi-line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1504.md +dotnet_diagnostic.SA1504.severity = warning + +# SA1505: Opening braces should not be followed by blank line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1505.md +dotnet_diagnostic.SA1505.severity = none + +# SA1506: Element documentation headers should not be followed by blank line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1506.md +dotnet_diagnostic.SA1506.severity = none + +# SA1507: Code should not contain multiple blank lines in a row +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1507.md +dotnet_diagnostic.SA1507.severity = warning + +# SA1508: Closing braces should not be preceded by blank line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1508.md +dotnet_diagnostic.SA1508.severity = none + +# SA1509: Opening braces should not be preceded by blank line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1509.md +dotnet_diagnostic.SA1509.severity = none + +# SA1510: Chained statement blocks should not be preceded by blank line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1510.md +dotnet_diagnostic.SA1510.severity = none + +# SA1511: While-do footer should not be preceded by blank line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1511.md +dotnet_diagnostic.SA1511.severity = none + +# SA1512: Single-line comments should not be followed by blank line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1512.md +dotnet_diagnostic.SA1512.severity = none + +# SA1513: Closing brace should be followed by blank line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1513.md +dotnet_diagnostic.SA1513.severity = none + +# SA1514: Element documentation header should be preceded by blank line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1514.md +dotnet_diagnostic.SA1514.severity = none + +# SA1515: Single-line comment should be preceded by blank line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1515.md +dotnet_diagnostic.SA1515.severity = none + +# SA1516: Elements should be separated by blank line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1516.md +dotnet_diagnostic.SA1516.severity = warning + +# SA1517: Code should not contain blank lines at start of file +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1517.md +dotnet_diagnostic.SA1517.severity = warning + +# SA1518: Use line endings correctly at end of file +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1518.md +dotnet_diagnostic.SA1518.severity = warning + +# SA1519: Braces should not be omitted from multi-line child statement +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1519.md +dotnet_diagnostic.SA1519.severity = none + +# SA1520: Use braces consistently +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1520.md +dotnet_diagnostic.SA1520.severity = none + +# SA1600: Elements should be documented +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1600.md +dotnet_diagnostic.SA1600.severity = none + +# SA1601: Partial elements should be documented +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1601.md +dotnet_diagnostic.SA1601.severity = none + +# SA1602: Enumeration items should be documented +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1602.md +dotnet_diagnostic.SA1602.severity = none + +# SA1604: Element documentation should have summary +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1604.md +dotnet_diagnostic.SA1604.severity = none + +# SA1605: Partial element documentation should have summary +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1605.md +dotnet_diagnostic.SA1605.severity = none + +# SA1606: Element documentation should have summary text +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1606.md +dotnet_diagnostic.SA1606.severity = none + +# SA1607: Partial element documentation should have summary text +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1607.md +dotnet_diagnostic.SA1607.severity = none + +# SA1608: Element documentation should not have default summary +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1608.md +dotnet_diagnostic.SA1608.severity = none + +# SA1609: Property documentation should have value +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1609.md +dotnet_diagnostic.SA1609.severity = none + +# SA1610: Property documentation should have value text +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1610.md +dotnet_diagnostic.SA1610.severity = none + +# SA1611: Element parameters should be documented +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1611.md +dotnet_diagnostic.SA1611.severity = none + +# SA1612: Element parameter documentation should match element parameters +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1612.md +dotnet_diagnostic.SA1612.severity = none + +# SA1613: Element parameter documentation should declare parameter name +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1613.md +dotnet_diagnostic.SA1613.severity = none + +# SA1614: Element parameter documentation should have text +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1614.md +dotnet_diagnostic.SA1614.severity = none + +# SA1615: Element return value should be documented +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1615.md +dotnet_diagnostic.SA1615.severity = none + +# SA1616: Element return value documentation should have text +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1616.md +dotnet_diagnostic.SA1616.severity = none + +# SA1617: Void return value should not be documented +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1617.md +dotnet_diagnostic.SA1617.severity = none + +# SA1618: Generic type parameters should be documented +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1618.md +dotnet_diagnostic.SA1618.severity = none + +# SA1619: Generic type parameters should be documented partial class +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1619.md +dotnet_diagnostic.SA1619.severity = none + +# SA1620: Generic type parameter documentation should match type parameters +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1620.md +dotnet_diagnostic.SA1620.severity = none + +# SA1621: Generic type parameter documentation should declare parameter name +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1621.md +dotnet_diagnostic.SA1621.severity = none + +# SA1622: Generic type parameter documentation should have text +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1622.md +dotnet_diagnostic.SA1622.severity = none + +# SA1623: Property summary documentation should match accessors +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1623.md +dotnet_diagnostic.SA1623.severity = none + +# SA1624: Property summary documentation should omit accessor with restricted access +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1624.md +dotnet_diagnostic.SA1624.severity = none + +# SA1625: Element documentation should not be copied and pasted +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1625.md +dotnet_diagnostic.SA1625.severity = none + +# SA1626: Single-line comments should not use documentation style slashes +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1626.md +dotnet_diagnostic.SA1626.severity = none + +# SA1627: Documentation text should not be empty +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1627.md +dotnet_diagnostic.SA1627.severity = none + +# SA1629: Documentation text should end with a period +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1629.md +dotnet_diagnostic.SA1629.severity = none + +# SA1633: File should have header +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1633.md +dotnet_diagnostic.SA1633.severity = none + +# SA1634: File header should show copyright +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1634.md +dotnet_diagnostic.SA1634.severity = none + +# SA1635: File header should have copyright text +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1635.md +dotnet_diagnostic.SA1635.severity = none + +# SA1636: File header copyright text should match +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1636.md +dotnet_diagnostic.SA1636.severity = none + +# SA1637: File header should contain file name +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1637.md +dotnet_diagnostic.SA1637.severity = none + +# SA1638: File header file name documentation should match file name +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1638.md +dotnet_diagnostic.SA1638.severity = none + +# SA1639: File header should have summary +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1639.md +dotnet_diagnostic.SA1639.severity = none + +# SA1640: File header should have valid company text +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1640.md +dotnet_diagnostic.SA1640.severity = none + +# SA1641: File header company name text should match +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1641.md +dotnet_diagnostic.SA1641.severity = none + +# SA1642: Constructor summary documentation should begin with standard text +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1642.md +dotnet_diagnostic.SA1642.severity = none + +# SA1643: Destructor summary documentation should begin with standard text +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1643.md +dotnet_diagnostic.SA1643.severity = warning + +# SA1648: inheritdoc should be used with inheriting class +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1648.md +dotnet_diagnostic.SA1648.severity = none + +# SA1649: File name should match first type name +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1649.md +dotnet_diagnostic.SA1649.severity = none + +# SA1651: Do not use placeholder elements +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1651.md +dotnet_diagnostic.SA1651.severity = none + +# SX1101: Do not prefix local calls with 'this.' +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SX1101.md +dotnet_diagnostic.SX1101.severity = none + +# SX1309: Field names should begin with underscore +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SX1309.md +dotnet_diagnostic.SX1309.severity = none + +# SX1309S: Static field names should begin with underscore +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SX1309S.md +dotnet_diagnostic.SX1309S.severity = none \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 7c69a18..0000000 --- a/.travis.yml +++ /dev/null @@ -1,22 +0,0 @@ -language: csharp - -git: - depth: 1000 - -os: - - linux -sudo: required -dist: trusty - -matrix: - fast_finish: true - -install: - - git clone https://github.com/PowerShell/PowerShell.git - - pushd PowerShell/tools - - ./install-powershell.sh - - popd - -script: - - ulimit -n 4096 - - pwsh -File ./TravisCI.ps1 diff --git a/Build.ps1 b/Build.ps1 new file mode 100644 index 0000000..f16b5b0 --- /dev/null +++ b/Build.ps1 @@ -0,0 +1,23 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +$buildOutputDirectory = "$PSScriptRoot\src\bin\Release" + +if ((Test-Path $buildOutputDirectory)) { + Remove-Item -Path $buildOutputDirectory -Recurse -Force +} + +# Perform dotnet build +dotnet build "$PSScriptRoot\src\Microsoft.PowerShell.Archive.csproj" -c Release + +"Build module location: $buildOutputDirectory" | Write-Verbose -Verbose + +# Get module version +$ManifestData = Import-PowerShellDataFile -Path "$buildOutputDirectory\Microsoft.PowerShell.Archive.psd1" +$Version = $ManifestData.ModuleVersion + +"Setting VSTS variable 'BuildOutDir' to '$buildOutputDirectory'" | Write-Verbose -Verbose +Write-Host "##vso[task.setvariable variable=BuildOutDir]$buildOutputDirectory" + +"Setting VSTS variable 'PackageVersion' to '$Version'" | Write-Verbose -Verbose +Write-Host "##vso[task.setvariable variable=PackageVersion]$Version" diff --git a/Microsoft.PowerShell.Archive.sln b/Microsoft.PowerShell.Archive.sln new file mode 100644 index 0000000..54234d6 --- /dev/null +++ b/Microsoft.PowerShell.Archive.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.3.32611.2 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerShell.Archive", "src\Microsoft.PowerShell.Archive.csproj", "{24838E64-AAC0-4E9E-B8B6-269AB1514046}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {24838E64-AAC0-4E9E-B8B6-269AB1514046}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {24838E64-AAC0-4E9E-B8B6-269AB1514046}.Debug|Any CPU.Build.0 = Debug|Any CPU + {24838E64-AAC0-4E9E-B8B6-269AB1514046}.Release|Any CPU.ActiveCfg = Release|Any CPU + {24838E64-AAC0-4E9E-B8B6-269AB1514046}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {BEAF8507-5CAD-4FEF-8BF4-691AC5FE4587} + EndGlobalSection +EndGlobal diff --git a/Microsoft.PowerShell.Archive/Microsoft.PowerShell.Archive.psd1 b/Microsoft.PowerShell.Archive/Microsoft.PowerShell.Archive.psd1 deleted file mode 100644 index 3260008..0000000 --- a/Microsoft.PowerShell.Archive/Microsoft.PowerShell.Archive.psd1 +++ /dev/null @@ -1,15 +0,0 @@ -@{ -GUID="eb74e8da-9ae2-482a-a648-e96550fb8733" -Author="Microsoft Corporation" -CompanyName="Microsoft Corporation" -Copyright="© Microsoft Corporation. All rights reserved." -Description='PowerShell module for working with ZIP archives.' -ModuleVersion="1.2.5" -PowerShellVersion="3.0" -FunctionsToExport = @('Compress-Archive', 'Expand-Archive') -DotNetFrameworkVersion = 4.5 -CmdletsToExport = @() -AliasesToExport = @() -NestedModules="Microsoft.PowerShell.Archive.psm1" -HelpInfoURI = 'https://go.microsoft.com/fwlink/?LinkId=2113631' -} diff --git a/Microsoft.PowerShell.Archive/Microsoft.PowerShell.Archive.psm1 b/Microsoft.PowerShell.Archive/Microsoft.PowerShell.Archive.psm1 deleted file mode 100644 index e7dd78d..0000000 --- a/Microsoft.PowerShell.Archive/Microsoft.PowerShell.Archive.psm1 +++ /dev/null @@ -1,1312 +0,0 @@ -data LocalizedData -{ - # culture="en-US" - ConvertFrom-StringData @' - PathNotFoundError=The path '{0}' either does not exist or is not a valid file system path. - ExpandArchiveInValidDestinationPath=The path '{0}' is not a valid file system directory path. - InvalidZipFileExtensionError={0} is not a supported archive file format. {1} is the only supported archive file format. - ArchiveFileIsReadOnly=The attributes of the archive file {0} is set to 'ReadOnly' hence it cannot be updated. If you intend to update the existing archive file, remove the 'ReadOnly' attribute on the archive file else use -Force parameter to override and create a new archive file. - ZipFileExistError=The archive file {0} already exists. Use the -Update parameter to update the existing archive file or use the -Force parameter to overwrite the existing archive file. - DuplicatePathFoundError=The input to {0} parameter contains a duplicate path '{1}'. Provide a unique set of paths as input to {2} parameter. - ArchiveFileIsEmpty=The archive file {0} is empty. - CompressProgressBarText=The archive file '{0}' creation is in progress... - ExpandProgressBarText=The archive file '{0}' expansion is in progress... - AppendArchiveFileExtensionMessage=The archive file path '{0}' supplied to the DestinationPath parameter does not include .zip extension. Hence .zip is appended to the supplied DestinationPath path and the archive file would be created at '{1}'. - AddItemtoArchiveFile=Adding '{0}'. - BadArchiveEntry=Can not process invalid archive entry '{0}'. - CreateFileAtExpandedPath=Created '{0}'. - InvalidArchiveFilePathError=The archive file path '{0}' specified as input to the {1} parameter is resolving to multiple file system paths. Provide a unique path to the {2} parameter where the archive file has to be created. - InvalidExpandedDirPathError=The directory path '{0}' specified as input to the DestinationPath parameter is resolving to multiple file system paths. Provide a unique path to the Destination parameter where the archive file contents have to be expanded. - FileExistsError=Failed to create file '{0}' while expanding the archive file '{1}' contents as the file '{2}' already exists. Use the -Force parameter if you want to overwrite the existing directory '{3}' contents when expanding the archive file. - DeleteArchiveFile=The partially created archive file '{0}' is deleted as it is not usable. - InvalidDestinationPath=The destination path '{0}' does not contain a valid archive file name. - PreparingToCompressVerboseMessage=Preparing to compress... - PreparingToExpandVerboseMessage=Preparing to expand... - ItemDoesNotAppearToBeAValidZipArchive=File '{0}' does not appear to be a valid zip archive. -'@ -} - -Import-LocalizedData LocalizedData -filename ArchiveResources -ErrorAction Ignore - -$zipFileExtension = ".zip" - -<############################################################################################ -# The Compress-Archive cmdlet can be used to zip/compress one or more files/directories. -############################################################################################> -function Compress-Archive -{ - [CmdletBinding( - DefaultParameterSetName="Path", - SupportsShouldProcess=$true, - HelpUri="https://go.microsoft.com/fwlink/?linkid=2096473")] - [OutputType([System.IO.File])] - param - ( - [parameter (mandatory=$true, Position=0, ParameterSetName="Path", ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] - [parameter (mandatory=$true, Position=0, ParameterSetName="PathWithForce", ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] - [parameter (mandatory=$true, Position=0, ParameterSetName="PathWithUpdate", ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] - [ValidateNotNullOrEmpty()] - [string[]] $Path, - - [parameter (mandatory=$true, ParameterSetName="LiteralPath", ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$true)] - [parameter (mandatory=$true, ParameterSetName="LiteralPathWithForce", ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$true)] - [parameter (mandatory=$true, ParameterSetName="LiteralPathWithUpdate", ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$true)] - [ValidateNotNullOrEmpty()] - [Alias("PSPath")] - [string[]] $LiteralPath, - - [parameter (mandatory=$true, - Position=1, - ValueFromPipeline=$false, - ValueFromPipelineByPropertyName=$false)] - [ValidateNotNullOrEmpty()] - [string] $DestinationPath, - - [parameter ( - mandatory=$false, - ValueFromPipeline=$false, - ValueFromPipelineByPropertyName=$false)] - [ValidateSet("Optimal","NoCompression","Fastest")] - [string] - $CompressionLevel = "Optimal", - - [parameter(mandatory=$true, ParameterSetName="PathWithUpdate", ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$false)] - [parameter(mandatory=$true, ParameterSetName="LiteralPathWithUpdate", ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$false)] - [switch] - $Update = $false, - - [parameter(mandatory=$true, ParameterSetName="PathWithForce", ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$false)] - [parameter(mandatory=$true, ParameterSetName="LiteralPathWithForce", ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$false)] - [switch] - $Force = $false, - - [switch] - $PassThru = $false - ) - - BEGIN - { - # Ensure the destination path is in a non-PS-specific format - $DestinationPath = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($DestinationPath) - - $inputPaths = @() - $destinationParentDir = [system.IO.Path]::GetDirectoryName($DestinationPath) - if($null -eq $destinationParentDir) - { - $errorMessage = ($LocalizedData.InvalidDestinationPath -f $DestinationPath) - ThrowTerminatingErrorHelper "InvalidArchiveFilePath" $errorMessage ([System.Management.Automation.ErrorCategory]::InvalidArgument) $DestinationPath - } - - if($destinationParentDir -eq [string]::Empty) - { - $destinationParentDir = '.' - } - - $archiveFileName = [system.IO.Path]::GetFileName($DestinationPath) - $destinationParentDir = GetResolvedPathHelper $destinationParentDir $false $PSCmdlet - - if($destinationParentDir.Count -gt 1) - { - $errorMessage = ($LocalizedData.InvalidArchiveFilePathError -f $DestinationPath, "DestinationPath", "DestinationPath") - ThrowTerminatingErrorHelper "InvalidArchiveFilePath" $errorMessage ([System.Management.Automation.ErrorCategory]::InvalidArgument) $DestinationPath - } - - IsValidFileSystemPath $destinationParentDir | Out-Null - $DestinationPath = Join-Path -Path $destinationParentDir -ChildPath $archiveFileName - - # GetExtension API does not validate for the actual existence of the path. - $extension = [system.IO.Path]::GetExtension($DestinationPath) - - # If user does not specify an extension, we append the .zip extension automatically. - If($extension -eq [string]::Empty) - { - $DestinationPathWithOutExtension = $DestinationPath - $DestinationPath = $DestinationPathWithOutExtension + $zipFileExtension - $appendArchiveFileExtensionMessage = ($LocalizedData.AppendArchiveFileExtensionMessage -f $DestinationPathWithOutExtension, $DestinationPath) - Write-Verbose $appendArchiveFileExtensionMessage - } - - $archiveFileExist = Test-Path -LiteralPath $DestinationPath -PathType Leaf - - if($archiveFileExist -and ($Update -eq $false -and $Force -eq $false)) - { - $errorMessage = ($LocalizedData.ZipFileExistError -f $DestinationPath) - ThrowTerminatingErrorHelper "ArchiveFileExists" $errorMessage ([System.Management.Automation.ErrorCategory]::InvalidArgument) $DestinationPath - } - - # If archive file already exists and if -Update is specified, then we check to see - # if we have write access permission to update the existing archive file. - if($archiveFileExist -and $Update -eq $true) - { - $item = Get-Item -Path $DestinationPath - if($item.Attributes.ToString().Contains("ReadOnly")) - { - $errorMessage = ($LocalizedData.ArchiveFileIsReadOnly -f $DestinationPath) - ThrowTerminatingErrorHelper "ArchiveFileIsReadOnly" $errorMessage ([System.Management.Automation.ErrorCategory]::InvalidOperation) $DestinationPath - } - } - - $isWhatIf = $psboundparameters.ContainsKey("WhatIf") - if(!$isWhatIf) - { - $preparingToCompressVerboseMessage = ($LocalizedData.PreparingToCompressVerboseMessage) - Write-Verbose $preparingToCompressVerboseMessage - - $progressBarStatus = ($LocalizedData.CompressProgressBarText -f $DestinationPath) - ProgressBarHelper "Compress-Archive" $progressBarStatus 0 100 100 1 - } - } - PROCESS - { - if($PsCmdlet.ParameterSetName -eq "Path" -or - $PsCmdlet.ParameterSetName -eq "PathWithForce" -or - $PsCmdlet.ParameterSetName -eq "PathWithUpdate") - { - $inputPaths += $Path - } - - if($PsCmdlet.ParameterSetName -eq "LiteralPath" -or - $PsCmdlet.ParameterSetName -eq "LiteralPathWithForce" -or - $PsCmdlet.ParameterSetName -eq "LiteralPathWithUpdate") - { - $inputPaths += $LiteralPath - } - } - END - { - # If archive file already exists and if -Force is specified, we delete the - # existing archive file and create a brand new one. - if(($PsCmdlet.ParameterSetName -eq "PathWithForce" -or - $PsCmdlet.ParameterSetName -eq "LiteralPathWithForce") -and $archiveFileExist) - { - Remove-Item -Path $DestinationPath -Force -ErrorAction Stop - } - - # Validate Source Path depending on parameter set being used. - # The specified source path contains one or more files or directories that needs - # to be compressed. - $isLiteralPathUsed = $false - if($PsCmdlet.ParameterSetName -eq "LiteralPath" -or - $PsCmdlet.ParameterSetName -eq "LiteralPathWithForce" -or - $PsCmdlet.ParameterSetName -eq "LiteralPathWithUpdate") - { - $isLiteralPathUsed = $true - } - - ValidateDuplicateFileSystemPath $PsCmdlet.ParameterSetName $inputPaths - $resolvedPaths = GetResolvedPathHelper $inputPaths $isLiteralPathUsed $PSCmdlet - IsValidFileSystemPath $resolvedPaths | Out-Null - - $sourcePath = $resolvedPaths; - - # CSVHelper: This is a helper function used to append comma after each path specified by - # the $sourcePath array. The comma separated paths are displayed in the -WhatIf message. - $sourcePathInCsvFormat = CSVHelper $sourcePath - if($pscmdlet.ShouldProcess($sourcePathInCsvFormat)) - { - try - { - # StopProcessing is not available in Script cmdlets. However the pipeline execution - # is terminated when ever 'CTRL + C' is entered by user to terminate the cmdlet execution. - # The finally block is executed whenever pipeline is terminated. - # $isArchiveFileProcessingComplete variable is used to track if 'CTRL + C' is entered by the - # user. - $isArchiveFileProcessingComplete = $false - - $numberOfItemsArchived = CompressArchiveHelper $sourcePath $DestinationPath $CompressionLevel $Update - - $isArchiveFileProcessingComplete = $true - } - finally - { - # The $isArchiveFileProcessingComplete would be set to $false if user has typed 'CTRL + C' to - # terminate the cmdlet execution or if an unhandled exception is thrown. - # $numberOfItemsArchived contains the count of number of files or directories add to the archive file. - # If the newly created archive file is empty then we delete it as it's not usable. - if(($isArchiveFileProcessingComplete -eq $false) -or - ($numberOfItemsArchived -eq 0)) - { - $DeleteArchiveFileMessage = ($LocalizedData.DeleteArchiveFile -f $DestinationPath) - Write-Verbose $DeleteArchiveFileMessage - - # delete the partial archive file created. - if (Test-Path $DestinationPath) { - Remove-Item -LiteralPath $DestinationPath -Force -Recurse -ErrorAction SilentlyContinue - } - } - elseif ($PassThru) - { - Get-Item -LiteralPath $DestinationPath - } - } - } - } -} - -<############################################################################################ -# The Expand-Archive cmdlet can be used to expand/extract an zip file. -############################################################################################> -function Expand-Archive -{ - [CmdletBinding( - DefaultParameterSetName="Path", - SupportsShouldProcess=$true, - HelpUri="https://go.microsoft.com/fwlink/?linkid=2096769")] - [OutputType([System.IO.FileSystemInfo])] - param - ( - [parameter ( - mandatory=$true, - Position=0, - ParameterSetName="Path", - ValueFromPipeline=$true, - ValueFromPipelineByPropertyName=$true)] - [ValidateNotNullOrEmpty()] - [string] $Path, - - [parameter ( - mandatory=$true, - ParameterSetName="LiteralPath", - ValueFromPipelineByPropertyName=$true)] - [ValidateNotNullOrEmpty()] - [Alias("PSPath")] - [string] $LiteralPath, - - [parameter (mandatory=$false, - Position=1, - ValueFromPipeline=$false, - ValueFromPipelineByPropertyName=$false)] - [ValidateNotNullOrEmpty()] - [string] $DestinationPath, - - [parameter (mandatory=$false, - ValueFromPipeline=$false, - ValueFromPipelineByPropertyName=$false)] - [switch] $Force, - - [switch] - $PassThru = $false - ) - - BEGIN - { - $isVerbose = $psboundparameters.ContainsKey("Verbose") - $isConfirm = $psboundparameters.ContainsKey("Confirm") - - $isDestinationPathProvided = $true - if($DestinationPath -eq [string]::Empty) - { - $resolvedDestinationPath = (Get-Location).ProviderPath - $isDestinationPathProvided = $false - } - else - { - $destinationPathExists = Test-Path -Path $DestinationPath -PathType Container - if($destinationPathExists) - { - $resolvedDestinationPath = GetResolvedPathHelper $DestinationPath $false $PSCmdlet - if($resolvedDestinationPath.Count -gt 1) - { - $errorMessage = ($LocalizedData.InvalidExpandedDirPathError -f $DestinationPath) - ThrowTerminatingErrorHelper "InvalidDestinationPath" $errorMessage ([System.Management.Automation.ErrorCategory]::InvalidArgument) $DestinationPath - } - - # At this point we are sure that the provided path resolves to a valid single path. - # Calling Resolve-Path again to get the underlying provider name. - $suppliedDestinationPath = Resolve-Path -Path $DestinationPath - if($suppliedDestinationPath.Provider.Name-ne "FileSystem") - { - $errorMessage = ($LocalizedData.ExpandArchiveInValidDestinationPath -f $DestinationPath) - ThrowTerminatingErrorHelper "InvalidDirectoryPath" $errorMessage ([System.Management.Automation.ErrorCategory]::InvalidArgument) $DestinationPath - } - } - else - { - $createdItem = New-Item -Path $DestinationPath -ItemType Directory -Confirm:$isConfirm -Verbose:$isVerbose -ErrorAction Stop - if($createdItem -ne $null -and $createdItem.PSProvider.Name -ne "FileSystem") - { - Remove-Item "$DestinationPath" -Force -Recurse -ErrorAction SilentlyContinue - $errorMessage = ($LocalizedData.ExpandArchiveInValidDestinationPath -f $DestinationPath) - ThrowTerminatingErrorHelper "InvalidDirectoryPath" $errorMessage ([System.Management.Automation.ErrorCategory]::InvalidArgument) $DestinationPath - } - - $resolvedDestinationPath = GetResolvedPathHelper $DestinationPath $true $PSCmdlet - } - } - - $isWhatIf = $psboundparameters.ContainsKey("WhatIf") - if(!$isWhatIf) - { - $preparingToExpandVerboseMessage = ($LocalizedData.PreparingToExpandVerboseMessage) - Write-Verbose $preparingToExpandVerboseMessage - - $progressBarStatus = ($LocalizedData.ExpandProgressBarText -f $DestinationPath) - ProgressBarHelper "Expand-Archive" $progressBarStatus 0 100 100 1 - } - } - PROCESS - { - switch($PsCmdlet.ParameterSetName) - { - "Path" - { - $resolvedSourcePaths = GetResolvedPathHelper $Path $false $PSCmdlet - - if($resolvedSourcePaths.Count -gt 1) - { - $errorMessage = ($LocalizedData.InvalidArchiveFilePathError -f $Path, $PsCmdlet.ParameterSetName, $PsCmdlet.ParameterSetName) - ThrowTerminatingErrorHelper "InvalidArchiveFilePath" $errorMessage ([System.Management.Automation.ErrorCategory]::InvalidArgument) $Path - } - } - "LiteralPath" - { - $resolvedSourcePaths = GetResolvedPathHelper $LiteralPath $true $PSCmdlet - - if($resolvedSourcePaths.Count -gt 1) - { - $errorMessage = ($LocalizedData.InvalidArchiveFilePathError -f $LiteralPath, $PsCmdlet.ParameterSetName, $PsCmdlet.ParameterSetName) - ThrowTerminatingErrorHelper "InvalidArchiveFilePath" $errorMessage ([System.Management.Automation.ErrorCategory]::InvalidArgument) $LiteralPath - } - } - } - - ValidateArchivePathHelper $resolvedSourcePaths - - if($pscmdlet.ShouldProcess($resolvedSourcePaths)) - { - $expandedItems = @() - - try - { - # StopProcessing is not available in Script cmdlets. However the pipeline execution - # is terminated when ever 'CTRL + C' is entered by user to terminate the cmdlet execution. - # The finally block is executed whenever pipeline is terminated. - # $isArchiveFileProcessingComplete variable is used to track if 'CTRL + C' is entered by the - # user. - $isArchiveFileProcessingComplete = $false - - # The User has not provided a destination path, hence we use '$pwd\ArchiveFileName' as the directory where the - # archive file contents would be expanded. If the path '$pwd\ArchiveFileName' already exists then we use the - # Windows default mechanism of appending a counter value at the end of the directory name where the contents - # would be expanded. - if(!$isDestinationPathProvided) - { - $archiveFile = New-Object System.IO.FileInfo $resolvedSourcePaths - $resolvedDestinationPath = Join-Path -Path $resolvedDestinationPath -ChildPath $archiveFile.BaseName - $destinationPathExists = Test-Path -LiteralPath $resolvedDestinationPath -PathType Container - - if(!$destinationPathExists) - { - New-Item -Path $resolvedDestinationPath -ItemType Directory -Confirm:$isConfirm -Verbose:$isVerbose -ErrorAction Stop | Out-Null - } - } - - ExpandArchiveHelper $resolvedSourcePaths $resolvedDestinationPath ([ref]$expandedItems) $Force $isVerbose $isConfirm - - $isArchiveFileProcessingComplete = $true - } - finally - { - # The $isArchiveFileProcessingComplete would be set to $false if user has typed 'CTRL + C' to - # terminate the cmdlet execution or if an unhandled exception is thrown. - if($isArchiveFileProcessingComplete -eq $false) - { - if($expandedItems.Count -gt 0) - { - # delete the expanded file/directory as the archive - # file was not completely expanded. - $expandedItems | % { Remove-Item "$_" -Force -Recurse } - } - } - elseif ($PassThru -and $expandedItems.Count -gt 0) - { - # Return the expanded items, being careful to remove trailing directory separators from - # any folder paths for consistency - $trailingDirSeparators = '\' + [System.IO.Path]::DirectorySeparatorChar + '+$' - Get-Item -LiteralPath ($expandedItems -replace $trailingDirSeparators) - } - } - } - } -} - -<############################################################################################ -# GetResolvedPathHelper: This is a helper function used to resolve the user specified Path. -# The path can either be absolute or relative path. -############################################################################################> -function GetResolvedPathHelper -{ - param - ( - [string[]] $path, - [boolean] $isLiteralPath, - [System.Management.Automation.PSCmdlet] - $callerPSCmdlet - ) - - $resolvedPaths =@() - - # null and empty check are are already done on Path parameter at the cmdlet layer. - foreach($currentPath in $path) - { - try - { - if($isLiteralPath) - { - $currentResolvedPaths = Resolve-Path -LiteralPath $currentPath -ErrorAction Stop - } - else - { - $currentResolvedPaths = Resolve-Path -Path $currentPath -ErrorAction Stop - } - } - catch - { - $errorMessage = ($LocalizedData.PathNotFoundError -f $currentPath) - $exception = New-Object System.InvalidOperationException $errorMessage, $_.Exception - $errorRecord = CreateErrorRecordHelper "ArchiveCmdletPathNotFound" $null ([System.Management.Automation.ErrorCategory]::InvalidArgument) $exception $currentPath - $callerPSCmdlet.ThrowTerminatingError($errorRecord) - } - - foreach($currentResolvedPath in $currentResolvedPaths) - { - $resolvedPaths += $currentResolvedPath.ProviderPath - } - } - - $resolvedPaths -} - -function Add-CompressionAssemblies { - Add-Type -AssemblyName System.IO.Compression - if ($psedition -eq "Core") - { - Add-Type -AssemblyName System.IO.Compression.ZipFile - } - else - { - Add-Type -AssemblyName System.IO.Compression.FileSystem - } -} - -function IsValidFileSystemPath -{ - param - ( - [string[]] $path - ) - - $result = $true; - - # null and empty check are are already done on Path parameter at the cmdlet layer. - foreach($currentPath in $path) - { - if(!([System.IO.File]::Exists($currentPath) -or [System.IO.Directory]::Exists($currentPath))) - { - $errorMessage = ($LocalizedData.PathNotFoundError -f $currentPath) - ThrowTerminatingErrorHelper "PathNotFound" $errorMessage ([System.Management.Automation.ErrorCategory]::InvalidArgument) $currentPath - } - } - - return $result; -} - - -function ValidateDuplicateFileSystemPath -{ - param - ( - [string] $inputParameter, - [string[]] $path - ) - - $uniqueInputPaths = @() - - # null and empty check are are already done on Path parameter at the cmdlet layer. - foreach($currentPath in $path) - { - $currentInputPath = $currentPath.ToUpper() - if($uniqueInputPaths.Contains($currentInputPath)) - { - $errorMessage = ($LocalizedData.DuplicatePathFoundError -f $inputParameter, $currentPath, $inputParameter) - ThrowTerminatingErrorHelper "DuplicatePathFound" $errorMessage ([System.Management.Automation.ErrorCategory]::InvalidArgument) $currentPath - } - else - { - $uniqueInputPaths += $currentInputPath - } - } -} - -function CompressionLevelMapper -{ - param - ( - [string] $compressionLevel - ) - - $compressionLevelFormat = [System.IO.Compression.CompressionLevel]::Optimal - - # CompressionLevel format is already validated at the cmdlet layer. - switch($compressionLevel.ToString()) - { - "Fastest" - { - $compressionLevelFormat = [System.IO.Compression.CompressionLevel]::Fastest - } - "NoCompression" - { - $compressionLevelFormat = [System.IO.Compression.CompressionLevel]::NoCompression - } - } - - return $compressionLevelFormat -} - -function CompressArchiveHelper -{ - param - ( - [string[]] $sourcePath, - [string] $destinationPath, - [string] $compressionLevel, - [bool] $isUpdateMode - ) - - $numberOfItemsArchived = 0 - $sourceFilePaths = @() - $sourceDirPaths = @() - - foreach($currentPath in $sourcePath) - { - $result = Test-Path -LiteralPath $currentPath -Type Leaf - if($result -eq $true) - { - $sourceFilePaths += $currentPath - } - else - { - $sourceDirPaths += $currentPath - } - } - - # The Source Path contains one or more directory (this directory can have files under it) and no files to be compressed. - if($sourceFilePaths.Count -eq 0 -and $sourceDirPaths.Count -gt 0) - { - $currentSegmentWeight = 100/[double]$sourceDirPaths.Count - $previousSegmentWeight = 0 - foreach($currentSourceDirPath in $sourceDirPaths) - { - $count = CompressSingleDirHelper $currentSourceDirPath $destinationPath $compressionLevel $true $isUpdateMode $previousSegmentWeight $currentSegmentWeight - $numberOfItemsArchived += $count - $previousSegmentWeight += $currentSegmentWeight - } - } - - # The Source Path contains only files to be compressed. - elseIf($sourceFilePaths.Count -gt 0 -and $sourceDirPaths.Count -eq 0) - { - # $previousSegmentWeight is equal to 0 as there are no prior segments. - # $currentSegmentWeight is set to 100 as all files have equal weightage. - $previousSegmentWeight = 0 - $currentSegmentWeight = 100 - - $numberOfItemsArchived = CompressFilesHelper $sourceFilePaths $destinationPath $compressionLevel $isUpdateMode $previousSegmentWeight $currentSegmentWeight - } - # The Source Path contains one or more files and one or more directories (this directory can have files under it) to be compressed. - elseif($sourceFilePaths.Count -gt 0 -and $sourceDirPaths.Count -gt 0) - { - # each directory is considered as an individual segments & all the individual files are clubed in to a separate segment. - $currentSegmentWeight = 100/[double]($sourceDirPaths.Count +1) - $previousSegmentWeight = 0 - - foreach($currentSourceDirPath in $sourceDirPaths) - { - $count = CompressSingleDirHelper $currentSourceDirPath $destinationPath $compressionLevel $true $isUpdateMode $previousSegmentWeight $currentSegmentWeight - $numberOfItemsArchived += $count - $previousSegmentWeight += $currentSegmentWeight - } - - $count = CompressFilesHelper $sourceFilePaths $destinationPath $compressionLevel $isUpdateMode $previousSegmentWeight $currentSegmentWeight - $numberOfItemsArchived += $count - } - - return $numberOfItemsArchived -} - -function CompressFilesHelper -{ - param - ( - [string[]] $sourceFilePaths, - [string] $destinationPath, - [string] $compressionLevel, - [bool] $isUpdateMode, - [double] $previousSegmentWeight, - [double] $currentSegmentWeight - ) - - $numberOfItemsArchived = ZipArchiveHelper $sourceFilePaths $destinationPath $compressionLevel $isUpdateMode $null $previousSegmentWeight $currentSegmentWeight - - return $numberOfItemsArchived -} - -function CompressSingleDirHelper -{ - param - ( - [string] $sourceDirPath, - [string] $destinationPath, - [string] $compressionLevel, - [bool] $useParentDirAsRoot, - [bool] $isUpdateMode, - [double] $previousSegmentWeight, - [double] $currentSegmentWeight - ) - - [System.Collections.Generic.List[System.String]]$subDirFiles = @() - - if($useParentDirAsRoot) - { - $sourceDirInfo = New-Object -TypeName System.IO.DirectoryInfo -ArgumentList $sourceDirPath - $sourceDirFullName = $sourceDirInfo.Parent.FullName - - # If the directory is present at the drive level the DirectoryInfo.Parent include directory separator. example: C:\ - # On the other hand if the directory exists at a deper level then DirectoryInfo.Parent - # has just the path (without an ending directory separator). example C:\source - if($sourceDirFullName.Length -eq 3) - { - $modifiedSourceDirFullName = $sourceDirFullName - } - else - { - $modifiedSourceDirFullName = $sourceDirFullName + [System.IO.Path]::DirectorySeparatorChar - } - } - else - { - $sourceDirFullName = $sourceDirPath - $modifiedSourceDirFullName = $sourceDirFullName + [System.IO.Path]::DirectorySeparatorChar - } - - $dirContents = Get-ChildItem -LiteralPath $sourceDirPath -Recurse - foreach($currentContent in $dirContents) - { - $isContainer = $currentContent -is [System.IO.DirectoryInfo] - if(!$isContainer) - { - $subDirFiles.Add($currentContent.FullName) - } - else - { - # The currentContent points to a directory. - # We need to check if the directory is an empty directory, if so such a - # directory has to be explicitly added to the archive file. - # if there are no files in the directory the GetFiles() API returns an empty array. - $files = $currentContent.GetFiles() - if($files.Count -eq 0) - { - $subDirFiles.Add($currentContent.FullName + [System.IO.Path]::DirectorySeparatorChar) - } - } - } - - $numberOfItemsArchived = ZipArchiveHelper $subDirFiles.ToArray() $destinationPath $compressionLevel $isUpdateMode $modifiedSourceDirFullName $previousSegmentWeight $currentSegmentWeight - - return $numberOfItemsArchived -} - -function ZipArchiveHelper -{ - param - ( - [System.Collections.Generic.List[System.String]] $sourcePaths, - [string] $destinationPath, - [string] $compressionLevel, - [bool] $isUpdateMode, - [string] $modifiedSourceDirFullName, - [double] $previousSegmentWeight, - [double] $currentSegmentWeight - ) - - $numberOfItemsArchived = 0 - $fileMode = [System.IO.FileMode]::Create - $result = Test-Path -LiteralPath $DestinationPath -Type Leaf - if($result -eq $true) - { - $fileMode = [System.IO.FileMode]::Open - } - - Add-CompressionAssemblies - - try - { - # At this point we are sure that the archive file has write access. - $archiveFileStreamArgs = @($destinationPath, $fileMode) - $archiveFileStream = New-Object -TypeName System.IO.FileStream -ArgumentList $archiveFileStreamArgs - - $zipArchiveArgs = @($archiveFileStream, [System.IO.Compression.ZipArchiveMode]::Update, $false) - $zipArchive = New-Object -TypeName System.IO.Compression.ZipArchive -ArgumentList $zipArchiveArgs - - $currentEntryCount = 0 - $progressBarStatus = ($LocalizedData.CompressProgressBarText -f $destinationPath) - $bufferSize = 4kb - $buffer = New-Object Byte[] $bufferSize - - foreach($currentFilePath in $sourcePaths) - { - if($modifiedSourceDirFullName -ne $null -and $modifiedSourceDirFullName.Length -gt 0) - { - $index = $currentFilePath.IndexOf($modifiedSourceDirFullName, [System.StringComparison]::OrdinalIgnoreCase) - $currentFilePathSubString = $currentFilePath.Substring($index, $modifiedSourceDirFullName.Length) - $relativeFilePath = $currentFilePath.Replace($currentFilePathSubString, "").Trim() - } - else - { - $relativeFilePath = [System.IO.Path]::GetFileName($currentFilePath) - } - - # Update mode is selected. - # Check to see if archive file already contains one or more zip files in it. - if($isUpdateMode -eq $true -and $zipArchive.Entries.Count -gt 0) - { - $entryToBeUpdated = $null - - # Check if the file already exists in the archive file. - # If so replace it with new file from the input source. - # If the file does not exist in the archive file then default to - # create mode and create the entry in the archive file. - - foreach($currentArchiveEntry in $zipArchive.Entries) - { - if(ArchivePathCompareHelper $currentArchiveEntry.FullName $relativeFilePath) - { - $entryToBeUpdated = $currentArchiveEntry - break - } - } - - if($entryToBeUpdated -ne $null) - { - $addItemtoArchiveFileMessage = ($LocalizedData.AddItemtoArchiveFile -f $currentFilePath) - $entryToBeUpdated.Delete() - } - } - - $compression = CompressionLevelMapper $compressionLevel - - # If a directory needs to be added to an archive file, - # by convention the .Net API's expect the path of the directory - # to end with directory separator to detect the path as an directory. - if(!$relativeFilePath.EndsWith([System.IO.Path]::DirectorySeparatorChar, [StringComparison]::OrdinalIgnoreCase)) - { - try - { - try - { - $currentFileStream = [System.IO.File]::Open($currentFilePath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::Read) - } - catch - { - # Failed to access the file. Write a non terminating error to the pipeline - # and move on with the remaining files. - $exception = $_.Exception - if($null -ne $_.Exception -and - $null -ne $_.Exception.InnerException) - { - $exception = $_.Exception.InnerException - } - $errorRecord = CreateErrorRecordHelper "CompressArchiveUnauthorizedAccessError" $null ([System.Management.Automation.ErrorCategory]::PermissionDenied) $exception $currentFilePath - Write-Error -ErrorRecord $errorRecord - } - - if($null -ne $currentFileStream) - { - $srcStream = New-Object System.IO.BinaryReader $currentFileStream - - $entryPath = DirectorySeparatorNormalizeHelper $relativeFilePath - $currentArchiveEntry = $zipArchive.CreateEntry($entryPath, $compression) - - # Updating the File Creation time so that the same timestamp would be retained after expanding the compressed file. - # At this point we are sure that Get-ChildItem would succeed. - $lastWriteTime = (Get-Item -LiteralPath $currentFilePath).LastWriteTime - if ($lastWriteTime.Year -lt 1980) - { - Write-Warning "'$currentFilePath' has LastWriteTime earlier than 1980. Compress-Archive will store any files with LastWriteTime values earlier than 1980 as 1/1/1980 00:00." - $lastWriteTime = [DateTime]::Parse('1980-01-01T00:00:00') - } - - $currentArchiveEntry.LastWriteTime = $lastWriteTime - - $destStream = New-Object System.IO.BinaryWriter $currentArchiveEntry.Open() - - while($numberOfBytesRead = $srcStream.Read($buffer, 0, $bufferSize)) - { - $destStream.Write($buffer, 0, $numberOfBytesRead) - $destStream.Flush() - } - - $numberOfItemsArchived += 1 - $addItemtoArchiveFileMessage = ($LocalizedData.AddItemtoArchiveFile -f $currentFilePath) - } - } - finally - { - If($null -ne $currentFileStream) - { - $currentFileStream.Dispose() - } - If($null -ne $srcStream) - { - $srcStream.Dispose() - } - If($null -ne $destStream) - { - $destStream.Dispose() - } - } - } - else - { - $entryPath = DirectorySeparatorNormalizeHelper $relativeFilePath - $currentArchiveEntry = $zipArchive.CreateEntry($entryPath, $compression) - $numberOfItemsArchived += 1 - $addItemtoArchiveFileMessage = ($LocalizedData.AddItemtoArchiveFile -f $currentFilePath) - } - - if($null -ne $addItemtoArchiveFileMessage) - { - Write-Verbose $addItemtoArchiveFileMessage - } - - $currentEntryCount += 1 - ProgressBarHelper "Compress-Archive" $progressBarStatus $previousSegmentWeight $currentSegmentWeight $sourcePaths.Count $currentEntryCount - } - } - finally - { - If($null -ne $zipArchive) - { - $zipArchive.Dispose() - } - - If($null -ne $archiveFileStream) - { - $archiveFileStream.Dispose() - } - - # Complete writing progress. - Write-Progress -Activity "Compress-Archive" -Completed - } - - return $numberOfItemsArchived -} - -<############################################################################################ -# ValidateArchivePathHelper: This is a helper function used to validate the archive file -# path & its file format. The only supported archive file format is .zip -############################################################################################> -function ValidateArchivePathHelper -{ - param - ( - [string] $archiveFile - ) - - if(-not [System.IO.File]::Exists($archiveFile)) - { - $errorMessage = ($LocalizedData.PathNotFoundError -f $archiveFile) - ThrowTerminatingErrorHelper "PathNotFound" $errorMessage ([System.Management.Automation.ErrorCategory]::InvalidArgument) $archiveFile - } -} - -<############################################################################################ -# ExpandArchiveHelper: This is a helper function used to expand the archive file contents -# to the specified directory. -############################################################################################> -function ExpandArchiveHelper -{ - param - ( - [string] $archiveFile, - [string] $expandedDir, - [ref] $expandedItems, - [boolean] $force, - [boolean] $isVerbose, - [boolean] $isConfirm - ) - - Add-CompressionAssemblies - - try - { - # The existence of archive file has already been validated by ValidateArchivePathHelper - # before calling this helper function. - $archiveFileStreamArgs = @($archiveFile, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read) - $archiveFileStream = New-Object -TypeName System.IO.FileStream -ArgumentList $archiveFileStreamArgs - - $zipArchiveArgs = @($archiveFileStream, [System.IO.Compression.ZipArchiveMode]::Read, $false) - try - { - $zipArchive = New-Object -TypeName System.IO.Compression.ZipArchive -ArgumentList $zipArchiveArgs - } - catch [System.IO.InvalidDataException] - { - # Failed to open the file for reading as a zip archive. Wrap the exception - # and re-throw it indicating it does not appear to be a valid zip file. - $exception = $_.Exception - if($null -ne $_.Exception -and - $null -ne $_.Exception.InnerException) - { - $exception = $_.Exception.InnerException - } - # Load the WindowsBase.dll assembly to get access to the System.IO.FileFormatException class - [System.Reflection.Assembly]::Load('WindowsBase,Version=4.0.0.0,Culture=neutral,PublicKeyToken=31bf3856ad364e35') - $invalidFileFormatException = New-Object -TypeName System.IO.FileFormatException -ArgumentList @( - ($LocalizedData.ItemDoesNotAppearToBeAValidZipArchive -f $archiveFile) - $exception - ) - throw $invalidFileFormatException - } - - if($zipArchive.Entries.Count -eq 0) - { - $archiveFileIsEmpty = ($LocalizedData.ArchiveFileIsEmpty -f $archiveFile) - Write-Verbose $archiveFileIsEmpty - return - } - - $currentEntryCount = 0 - $progressBarStatus = ($LocalizedData.ExpandProgressBarText -f $archiveFile) - - # Ensures that the last character on the extraction path is the directory separator char. - # Without this, a bad zip file could try to traverse outside of the expected extraction path. - # At this point $expandedDir is a fully qualified path without any relative segments. - if (-not $expandedDir.EndsWith([System.IO.Path]::DirectorySeparatorChar)) - { - $expandedDir += [System.IO.Path]::DirectorySeparatorChar - } - - # The archive entries can either be empty directories or files. - foreach($currentArchiveEntry in $zipArchive.Entries) - { - # Windows filesystem provider will internally convert from `/` to `\` - $currentArchiveEntryPath = Join-Path -Path $expandedDir -ChildPath $currentArchiveEntry.FullName - - # Remove possible relative segments from target - # This is similar to [System.IO.Path]::GetFullPath($currentArchiveEntryPath) but uses PS current dir instead of process-wide current dir - $currentArchiveEntryPath = $PSCmdlet.SessionState.Path.GetUnresolvedProviderPathFromPSPath($currentArchiveEntryPath) - - # Check that expanded relative paths and absolute paths from the archive are Not going outside of target directory - # Ordinal match is safest, case-sensitive volumes can be mounted within volumes that are case-insensitive. - if (-not ($currentArchiveEntryPath.StartsWith($expandedDir, [System.StringComparison]::Ordinal))) - { - $BadArchiveEntryMessage = ($LocalizedData.BadArchiveEntry -f $currentArchiveEntry.FullName) - # notify user of bad archive entry - Write-Error $BadArchiveEntryMessage - # move on to the next entry in the archive - continue - } - - $extension = [system.IO.Path]::GetExtension($currentArchiveEntryPath) - - # The current archive entry is an empty directory - # The FullName of the Archive Entry representing a directory would end with a trailing directory separator. - if($extension -eq [string]::Empty -and - $currentArchiveEntryPath.EndsWith([System.IO.Path]::DirectorySeparatorChar, [StringComparison]::OrdinalIgnoreCase)) - { - $pathExists = Test-Path -LiteralPath $currentArchiveEntryPath - - # The current archive entry expects an empty directory. - # Check if the existing directory is empty. If it's not empty - # then it means that user has added this directory by other means. - if($pathExists -eq $false) - { - New-Item $currentArchiveEntryPath -Type Directory -Confirm:$isConfirm | Out-Null - - if(Test-Path -LiteralPath $currentArchiveEntryPath -PathType Container) - { - $addEmptyDirectorytoExpandedPathMessage = ($LocalizedData.AddItemtoArchiveFile -f $currentArchiveEntryPath) - Write-Verbose $addEmptyDirectorytoExpandedPathMessage - - $expandedItems.Value += $currentArchiveEntryPath - } - } - } - else - { - try - { - $currentArchiveEntryFileInfo = New-Object -TypeName System.IO.FileInfo -ArgumentList $currentArchiveEntryPath - $parentDirExists = Test-Path -LiteralPath $currentArchiveEntryFileInfo.DirectoryName -PathType Container - - # If the Parent directory of the current entry in the archive file does not exist, then create it. - if($parentDirExists -eq $false) - { - # note that if any ancestor of this directory doesn't exist, we don't recursively create each one as New-Item - # takes care of this already, so only one DirectoryInfo is returned instead of one for each parent directory - # that only contains directories - New-Item $currentArchiveEntryFileInfo.DirectoryName -Type Directory -Confirm:$isConfirm | Out-Null - - if(!(Test-Path -LiteralPath $currentArchiveEntryFileInfo.DirectoryName -PathType Container)) - { - # The directory referred by $currentArchiveEntryFileInfo.DirectoryName was not successfully created. - # This could be because the user has specified -Confirm parameter when Expand-Archive was invoked - # and authorization was not provided when confirmation was prompted. In such a scenario, - # we skip the current file in the archive and continue with the remaining archive file contents. - Continue - } - - $expandedItems.Value += $currentArchiveEntryFileInfo.DirectoryName - } - - $hasNonTerminatingError = $false - - # Check if the file in to which the current archive entry contents - # would be expanded already exists. - if($currentArchiveEntryFileInfo.Exists) - { - if($force) - { - Remove-Item -LiteralPath $currentArchiveEntryFileInfo.FullName -Force -ErrorVariable ev -Verbose:$isVerbose -Confirm:$isConfirm - if($ev -ne $null) - { - $hasNonTerminatingError = $true - } - - if(Test-Path -LiteralPath $currentArchiveEntryFileInfo.FullName -PathType Leaf) - { - # The file referred by $currentArchiveEntryFileInfo.FullName was not successfully removed. - # This could be because the user has specified -Confirm parameter when Expand-Archive was invoked - # and authorization was not provided when confirmation was prompted. In such a scenario, - # we skip the current file in the archive and continue with the remaining archive file contents. - Continue - } - } - else - { - # Write non-terminating error to the pipeline. - $errorMessage = ($LocalizedData.FileExistsError -f $currentArchiveEntryFileInfo.FullName, $archiveFile, $currentArchiveEntryFileInfo.FullName, $currentArchiveEntryFileInfo.FullName) - $errorRecord = CreateErrorRecordHelper "ExpandArchiveFileExists" $errorMessage ([System.Management.Automation.ErrorCategory]::InvalidOperation) $null $currentArchiveEntryFileInfo.FullName - Write-Error -ErrorRecord $errorRecord - $hasNonTerminatingError = $true - } - } - - if(!$hasNonTerminatingError) - { - # The ExtractToFile() method doesn't handle whitespace correctly, strip whitespace which is consistent with how Explorer handles archives - # There is an edge case where an archive contains files whose only difference is whitespace, but this is uncommon and likely not legitimate - [string[]] $parts = $currentArchiveEntryPath.Split([System.IO.Path]::DirectorySeparatorChar) | % { $_.Trim() } - $currentArchiveEntryPath = [string]::Join([System.IO.Path]::DirectorySeparatorChar, $parts) - - [System.IO.Compression.ZipFileExtensions]::ExtractToFile($currentArchiveEntry, $currentArchiveEntryPath, $false) - - # Add the expanded file path to the $expandedItems array, - # to keep track of all the expanded files created while expanding the archive file. - # If user enters CTRL + C then at that point of time, all these expanded files - # would be deleted as part of the clean up process. - $expandedItems.Value += $currentArchiveEntryPath - - $addFiletoExpandedPathMessage = ($LocalizedData.CreateFileAtExpandedPath -f $currentArchiveEntryPath) - Write-Verbose $addFiletoExpandedPathMessage - } - } - finally - { - If($null -ne $destStream) - { - $destStream.Dispose() - } - - If($null -ne $srcStream) - { - $srcStream.Dispose() - } - } - } - - $currentEntryCount += 1 - # $currentSegmentWeight is Set to 100 giving equal weightage to each file that is getting expanded. - # $previousSegmentWeight is set to 0 as there are no prior segments. - $previousSegmentWeight = 0 - $currentSegmentWeight = 100 - ProgressBarHelper "Expand-Archive" $progressBarStatus $previousSegmentWeight $currentSegmentWeight $zipArchive.Entries.Count $currentEntryCount - } - } - finally - { - If($null -ne $zipArchive) - { - $zipArchive.Dispose() - } - - If($null -ne $archiveFileStream) - { - $archiveFileStream.Dispose() - } - - # Complete writing progress. - Write-Progress -Activity "Expand-Archive" -Completed - } -} - -<############################################################################################ -# ProgressBarHelper: This is a helper function used to display progress message. -# This function is used by both Compress-Archive & Expand-Archive to display archive file -# creation/expansion progress. -############################################################################################> -function ProgressBarHelper -{ - param - ( - [string] $cmdletName, - [string] $status, - [double] $previousSegmentWeight, - [double] $currentSegmentWeight, - [int] $totalNumberofEntries, - [int] $currentEntryCount - ) - - if($currentEntryCount -gt 0 -and - $totalNumberofEntries -gt 0 -and - $previousSegmentWeight -ge 0 -and - $currentSegmentWeight -gt 0) - { - $entryDefaultWeight = $currentSegmentWeight/[double]$totalNumberofEntries - - $percentComplete = $previousSegmentWeight + ($entryDefaultWeight * $currentEntryCount) - Write-Progress -Activity $cmdletName -Status $status -PercentComplete $percentComplete - } -} - -<############################################################################################ -# CSVHelper: This is a helper function used to append comma after each path specified by -# the SourcePath array. This helper function is used to display all the user supplied paths -# in the WhatIf message. -############################################################################################> -function CSVHelper -{ - param - ( - [string[]] $sourcePath - ) - - # SourcePath has already been validated by the calling function. - if($sourcePath.Count -gt 1) - { - $sourcePathInCsvFormat = "`n" - for($currentIndex=0; $currentIndex -lt $sourcePath.Count; $currentIndex++) - { - if($currentIndex -eq $sourcePath.Count - 1) - { - $sourcePathInCsvFormat += $sourcePath[$currentIndex] - } - else - { - $sourcePathInCsvFormat += $sourcePath[$currentIndex] + "`n" - } - } - } - else - { - $sourcePathInCsvFormat = $sourcePath - } - - return $sourcePathInCsvFormat -} - -<############################################################################################ -# ThrowTerminatingErrorHelper: This is a helper function used to throw terminating error. -############################################################################################> -function ThrowTerminatingErrorHelper -{ - param - ( - [string] $errorId, - [string] $errorMessage, - [System.Management.Automation.ErrorCategory] $errorCategory, - [object] $targetObject, - [Exception] $innerException - ) - - if($innerException -eq $null) - { - $exception = New-object System.IO.IOException $errorMessage - } - else - { - $exception = New-Object System.IO.IOException $errorMessage, $innerException - } - - $exception = New-Object System.IO.IOException $errorMessage - $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $errorId, $errorCategory, $targetObject - $PSCmdlet.ThrowTerminatingError($errorRecord) -} - -<############################################################################################ -# CreateErrorRecordHelper: This is a helper function used to create an ErrorRecord -############################################################################################> -function CreateErrorRecordHelper -{ - param - ( - [string] $errorId, - [string] $errorMessage, - [System.Management.Automation.ErrorCategory] $errorCategory, - [Exception] $exception, - [object] $targetObject - ) - - if($null -eq $exception) - { - $exception = New-Object System.IO.IOException $errorMessage - } - - $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $errorId, $errorCategory, $targetObject - return $errorRecord -} - -<############################################################################################ -# DirectorySeparatorNormalizeHelper: This is a helper function used to normalize separators -# when compressing archives, creating cross platform archives. -# -# The approach taken is leveraging the fact that .net on Windows all the way back to -# Framework 1.1 specifies `\` as DirectoryPathSeparatorChar and `/` as -# AltDirectoryPathSeparatorChar, while other platforms in .net Core use `/` for -# DirectoryPathSeparatorChar and AltDirectoryPathSeparatorChar. When using a *nix platform, -# the replacements will be no-ops, while Windows will convert all `\` to `/` for the -# purposes of the ZipEntry FullName. -############################################################################################> -function DirectorySeparatorNormalizeHelper -{ - param - ( - [string] $archivePath - ) - - if($null -eq $archivePath) - { - return $archivePath - } - - return $archivePath.replace([System.IO.Path]::DirectorySeparatorChar, [System.IO.Path]::AltDirectorySeparatorChar) -} - -<############################################################################################ -# ArchivePathCompareHelper: This is a helper function used to compare with normalized -# separators. -############################################################################################> -function ArchivePathCompareHelper -{ - param - ( - [string] $pathArgA, - [string] $pathArgB - ) - - $normalizedPathArgA = DirectorySeparatorNormalizeHelper $pathArgA - $normalizedPathArgB = DirectorySeparatorNormalizeHelper $pathArgB - - return $normalizedPathArgA -eq $normalizedPathArgB -} diff --git a/Microsoft.PowerShell.Archive/en-US/ArchiveResources.psd1 b/Microsoft.PowerShell.Archive/en-US/ArchiveResources.psd1 deleted file mode 100644 index d3b713d..0000000 --- a/Microsoft.PowerShell.Archive/en-US/ArchiveResources.psd1 +++ /dev/null @@ -1,26 +0,0 @@ -# Localized ArchiveResources.psd1 - -ConvertFrom-StringData @' -###PSLOC -PathNotFoundError=The path '{0}' either does not exist or is not a valid file system path. -ExpandArchiveInValidDestinationPath=The path '{0}' is not a valid file system directory path. -InvalidZipFileExtensionError={0} is not a supported archive file format. {1} is the only supported archive file format. -ArchiveFileIsReadOnly=The attributes of the archive file {0} is set to 'ReadOnly' hence it cannot be updated. If you intend to update the existing archive file, remove the 'ReadOnly' attribute on the archive file else use -Force parameter to override and create a new archive file. -ZipFileExistError=The archive file {0} already exists. Use the -Update parameter to update the existing archive file or use the -Force parameter to overwrite the existing archive file. -DuplicatePathFoundError=The input to {0} parameter contains a duplicate path '{1}'. Provide a unique set of paths as input to {2} parameter. -ArchiveFileIsEmpty=The archive file {0} is empty. -CompressProgressBarText=The archive file '{0}' creation is in progress... -ExpandProgressBarText=The archive file '{0}' expansion is in progress... -AppendArchiveFileExtensionMessage=The archive file path '{0}' supplied to the DestinationPath parameter does not include .zip extension. Hence .zip is appended to the supplied DestinationPath path and the archive file would be created at '{1}'. -AddItemtoArchiveFile=Adding '{0}'. -BadArchiveEntry=Can not process invalid archive entry '{0}'. -CreateFileAtExpandedPath=Created '{0}'. -InvalidArchiveFilePathError=The archive file path '{0}' specified as input to the {1} parameter is resolving to multiple file system paths. Provide a unique path to the {2} parameter where the archive file has to be created. -InvalidExpandedDirPathError=The directory path '{0}' specified as input to the DestinationPath parameter is resolving to multiple file system paths. Provide a unique path to the Destination parameter where the archive file contents have to be expanded. -FileExistsError=Failed to create file '{0}' while expanding the archive file '{1}' contents as the file '{2}' already exists. Use the -Force parameter if you want to overwrite the existing directory '{3}' contents when expanding the archive file. -DeleteArchiveFile=The partially created archive file '{0}' is deleted as it is not usable. -InvalidDestinationPath=The destination path '{0}' does not contain a valid archive file name. -PreparingToCompressVerboseMessage=Preparing to compress... -PreparingToExpandVerboseMessage=Preparing to expand... -###PSLOC -'@ diff --git a/README.md b/README.md index e3c7b8e..53a6e45 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,9 @@ # Microsoft.PowerShell.Archive Module [Microsoft.PowerShell.Archive module](https://technet.microsoft.com/en-us/library/dn818910.aspx) contains cmdlets that let you create and extract ZIP archives. -|AppVeyor (Windows) | Travis CI (Linux) | -|:-------------------:|:------------------:| -|[![Build status](https://ci.appveyor.com/api/projects/status/npvhboe2nbdbtteg/branch/master?svg=true)](https://ci.appveyor.com/project/PowerShell/microsoft-powershell-archive/branch/master)|[![Build Status](https://travis-ci.org/PowerShell/Microsoft.PowerShell.Archive.svg?branch=master)](https://travis-ci.org/PowerShell/Microsoft.PowerShell.Archive)| - +| Azure CI | +|:-------------------:| +|[![Build Status](https://dev.azure.com/powershell/Archive/_apis/build/status/PowerShell.Microsoft.PowerShell.Archive?repoName=PowerShell%2FMicrosoft.PowerShell.Archive&branchName=refs%2Fpull%2F131%2Fmerge)](https://dev.azure.com/powershell/Archive/_build/latest?definitionId=130&repoName=PowerShell%2FMicrosoft.PowerShell.Archive&branchName=refs%2Fpull%2F131%2Fmerge)| ## [Compress-Archive](https://technet.microsoft.com/library/dn841358.aspx) examples 1. Create an archive from an entire folder including subdirectories: `Compress-Archive -Path C:\Reference -DestinationPath C:\Archives\Draft.zip` 2. Update an existing archive file: `Compress-Archive -Path C:\Reference\* -DestinationPath C:\Archives\Draft.zip -Update` diff --git a/SimpleBuild.ps1 b/SimpleBuild.ps1 deleted file mode 100644 index 9ef520b..0000000 --- a/SimpleBuild.ps1 +++ /dev/null @@ -1,20 +0,0 @@ -if ((Test-Path "$PSScriptRoot\out")) { - Remove-Item -Path $PSScriptRoot\out -Recurse -Force -} - -New-Item -ItemType directory -Path $PSScriptRoot\out | Out-Null -New-Item -ItemType directory -Path $PSScriptRoot\out\Microsoft.PowerShell.Archive | Out-Null - -$OutPath = Join-Path $PSScriptRoot "out" -$OutModulePath = Join-Path $OutPath "Microsoft.PowerShell.Archive" - -Copy-Item -Recurse -Path "$PSScriptRoot\Microsoft.PowerShell.Archive" -Destination $OutPath -Force - -"Build module location: $OutModulePath" | Write-Verbose -Verbose - -"Setting VSTS variable 'BuildOutDir' to '$OutModulePath'" | Write-Verbose -Verbose -Write-Host "##vso[task.setvariable variable=BuildOutDir]$OutModulePath" - -$psd1ModuleVersion = (Get-Content -Path "$OutModulePath\Microsoft.PowerShell.Archive.psd1" | Select-String 'ModuleVersion="(.*)"').Matches[0].Groups[1].Value -"Setting VSTS variable 'PackageVersion' to '$psd1ModuleVersion'" | Write-Verbose -Verbose -Write-Host "##vso[task.setvariable variable=PackageVersion]$psd1ModuleVersion" diff --git a/Tests/Assertions/Should-BeZipArchiveOnlyContaining.psm1 b/Tests/Assertions/Should-BeZipArchiveOnlyContaining.psm1 new file mode 100644 index 0000000..833fc6c --- /dev/null +++ b/Tests/Assertions/Should-BeZipArchiveOnlyContaining.psm1 @@ -0,0 +1,148 @@ +function Should-BeZipArchiveOnlyContaining { + <# + .SYNOPSIS + Checks if a zip archive contains the entries $ExpectedValue + .EXAMPLE + "C:\Users\\archive.zip" | Should -BeZipArchiveContaining @("file1.txt") + + Checks if archive.zip only contains file1.txt + #> + + [CmdletBinding()] + Param ( + [string] $ActualValue, + [string[]] $ExpectedValue, + [switch] $Negate, + [string] $Because, + [switch] $LiteralPath, + $CallerSessionState + ) + + # ActualValue is supposed to be a path to an archive + # It could be a path to a custom PSDrive, so it needes to be converted + if ($LiteralPath) { + $ActualValue = Convert-Path -LiteralPath $ActualValue + } else { + $ActualValue = Convert-Path -Path $ActualValue + } + + + # Ensure ActualValue is a valid path + if ($LiteralPath) { + $testPathResult = Test-Path -LiteralPath $ActualValue + } else { + $testPathResult = Test-Path -Path $ActualValue + } + + # Don't continue processing if ActualValue is not an actual path + # Determine if the assertion succeeded or failed and then return + if (-not $testPathResult) { + $succeeded = $Negate + if (-not $succeeded) { + $failureMessage = "The path ${ActualValue} does not exist" + } + return [pscustomobject]@{ + Succeeded = $succeeded + FailureMessage = $failureMessage + } + } + + # Get 7-zip to list the contents of the archive + if ($IsWindows) { + $output = 7z.exe l $ActualValue -ba + } else { + $output = 7z l $ActualValue -ba + } + + # Check if the output is null + if ($null -eq $output) { + if ($null -eq $ExpectedValue -or $ExpectedValue.Length -eq 0) { + $succeeded = -not $Negate + } else { + $succeeded = $Negate + } + + if (-not $succeeded) { + $failureMessage = "Archive {0} contains nothing, but it was expected to contain something" + } + + return [pscustomobject]@{ + Succeeded = $succeeded + FailureMessage = $failureMessage + } + } + + # Filter the output line by line + $lines = $output -split [System.Environment]::NewLine + + # Stores the entry names + $entryNames = @() + + # Go through each line and split it by whitespace + foreach ($line in $lines) { + $lineComponents = $line -split " +" + + # Example of some lines: + #2022-08-05 15:54:04 D.... 0 0 SourceDir + #2022-08-05 15:54:04 ..... 11 11 SourceDir/Sample-1.txt + + # First component is date + # 2nd component is time + # 3rd componnent is attributes + # 4th component is size + # 5th component is compressed size + # 6th component is entry name + + $entryName = $lineComponents[$lineComponents.Length - 1] + + # Since 7zip does not show trailing forwardslash for directories, we need to check the attributes to see if it starts with 'D' + # If so, it means the entry is a directory and we should append a forwardslash to the entry name + + if ($lineComponents[2].StartsWith('D')) { + $entryName += '/' + } + + # Replace backslashes to forwardslashes + $dirSeperatorChar = [System.IO.Path]::DirectorySeparatorChar + $entryName = $entryName.Replace($dirSeperatorChar, "/") + + $entryNames += $entryName + } + + $itemsNotInArchive = @() + + # Go through each item in ExpectedValue and ensure it is in entryNames + foreach ($expectedItem in $ExpectedValue) { + if ($entryNames -notcontains $expectedItem) { + $itemsNotInArchive += $expectedItem + } + } + + if ($itemsNotInArchive.Length -gt 0 -and -not $Negate) { + # Create a comma-seperated string from $itemsNotInEnryName + $commaSeperatedItemsNotInArchive = $itemsNotInArchive -join "," + $failureMessage = "'$ActualValue' does not contain $commaSeperatedItemsNotInArchive $(if($Because) { "because $Because"})." + $succeeded = $false + } + + # Ensure the length of $entryNames is equal to that of $ExpectedValue + if ($null -eq $succeeded -and $entryNames.Length -ne $ExpectedValue.Length -and -not $Negate) { + $failureMessage = "${ActualValue} does not contain the same number of items as ${ExpectedValue -join ""} (expected ${ExpectedValue.Length} entries but found ${entryNames.Length}) $(if($Because) { "because $Because"})." + $succeeded = $false + } + + if ($null -eq $succeeded) { + $succeeded = -not $Negate + if (-not $succeeded) { + $failureMessage = "Expected ${ActualValue} to not contain the entries ${ExpectedValue -join ""} only $(if($Because) { "because $Because"})." + } + } + + $ObjProperties = @{ + Succeeded = $succeeded + FailureMessage = $failureMessage + } + return New-Object PSObject -Property $ObjProperties +} + +Add-ShouldOperator -Name BeZipArchiveOnlyContaining -InternalName 'Should-BeZipArchiveOnlyContaining' -Test ${function:Should-BeZipArchiveOnlyContaining} \ No newline at end of file diff --git a/Tests/Compress-Archive.Tests.ps1 b/Tests/Compress-Archive.Tests.ps1 new file mode 100644 index 0000000..cfa3ad9 --- /dev/null +++ b/Tests/Compress-Archive.Tests.ps1 @@ -0,0 +1,560 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +BeforeDiscovery { + # Loads and registers custom assertion. Ignores usage of unapproved verb with -DisableNameChecking + Import-Module "$PSScriptRoot/Assertions/Should-BeZipArchiveOnlyContaining.psm1" -DisableNameChecking +} + + Describe("Microsoft.PowerShell.Archive tests") { + BeforeAll { + + $originalProgressPref = $ProgressPreference + $ProgressPreference = "SilentlyContinue" + $originalPSModulePath = $env:PSModulePath + } + + AfterAll { + $global:ProgressPreference = $originalProgressPref + $env:PSModulePath = $originalPSModulePath + } + + Context "Parameter set validation tests" { + BeforeAll { + # Set up files for tests + New-Item TestDrive:/SourceDir -Type Directory + "Some Data" | Out-File -FilePath TestDrive:/SourceDir/Sample-1.txt + New-Item TestDrive:/EmptyDirectory -Type Directory | Out-Null + } + + + It "Validate errors from Compress-Archive with null and empty values for Path, LiteralPath, and DestinationPath parameters" -ForEach @( + @{ Path = $null; DestinationPath = "TestDrive:/archive1.zip" } + @{ Path = "TestDrive:/SourceDir"; DestinationPath = $null } + @{ Path = $null; DestinationPath = $null } + @{ Path = ""; DestinationPath = "TestDrive:/archive1.zip" } + @{ Path = "TestDrive:/SourceDir"; DestinationPath = "" } + @{ Path = ""; DestinationPath = "" } + ) { + try + { + Compress-Archive -Path $Path -DestinationPath $DestinationPath + throw "ValidateNotNullOrEmpty attribute is missing on one of parameters belonging to LiteralPath parameterset." + } + catch + { + $_.FullyQualifiedErrorId | Should -Be "ParameterArgumentValidationError,Microsoft.PowerShell.Archive.CompressArchiveCommand" + } + + try + { + Compress-Archive -LiteralPath $Path -DestinationPath $DestinationPath + throw "ValidateNotNullOrEmpty attribute is missing on one of parameters belonging to LiteralPath parameterset." + } + catch + { + $_.FullyQualifiedErrorId | Should -Be "ParameterArgumentValidationError,Microsoft.PowerShell.Archive.CompressArchiveCommand" + } + } + + It "Validate errors from Compress-Archive when invalid path is supplied for Path or LiteralPath parameters" -ForEach @( + @{ Path = "Variable:/PWD" } + @{ Path = @("TestDrive:/", "Variable:/PWD") } + ) { + $DestinationPath = "TestDrive:/archive2.zip" + + Compress-Archive -Path $Path -DestinationPath $DestinationPath -ErrorAction SilentlyContinue -ErrorVariable error + $error.Count | Should -Be 1 + $error[0].FullyQualifiedErrorId | Should -Be "InvalidPath,Microsoft.PowerShell.Archive.CompressArchiveCommand" + Remove-Item -Path $DestinationPath + + Compress-Archive -LiteralPath $Path -DestinationPath $DestinationPath -ErrorAction SilentlyContinue -ErrorVariable error + $error.Count | Should -Be 1 + $error[0].FullyQualifiedErrorId | Should -Be "InvalidPath,Microsoft.PowerShell.Archive.CompressArchiveCommand" + Remove-Item -Path $DestinationPath + } + + It "Throws terminating error when non-existing path is supplied for Path or LiteralPath parameters" -ForEach @( + @{ Path = "TestDrive:/DoesNotExist" } + @{ Path = @("TestDrive:/", "TestDrive:/DoesNotExist") } + ) -Tag this2 { + $DestinationPath = "TestDrive:/archive3.zip" + + try + { + Compress-Archive -Path $Path -DestinationPath $DestinationPath + throw "Failed to validate that an invalid Path was supplied as input to Compress-Archive cmdlet." + } + catch + { + $_.FullyQualifiedErrorId | Should -Be "PathNotFound,Microsoft.PowerShell.Archive.CompressArchiveCommand" + } + + try + { + Compress-Archive -LiteralPath $Path -DestinationPath $DestinationPath + throw "Failed to validate that an invalid LiteralPath was supplied as input to Compress-Archive cmdlet." + } + catch + { + $_.FullyQualifiedErrorId | Should -Be "PathNotFound,Microsoft.PowerShell.Archive.CompressArchiveCommand" + } + } + + It "Validate error from Compress-Archive when duplicate paths are supplied as input to Path parameter" { + $sourcePath = @( + "TestDrive:/SourceDir/Sample-1.txt", + "TestDrive:/SourceDir/Sample-1.txt") + $destinationPath = "TestDrive:/DuplicatePaths.zip" + + try + { + Compress-Archive -Path $sourcePath -DestinationPath $destinationPath + throw "Failed to detect that duplicate Path $sourcePath is supplied as input to Path parameter." + } + catch + { + $_.FullyQualifiedErrorId | Should -Be "DuplicatePaths,Microsoft.PowerShell.Archive.CompressArchiveCommand" + } + } + + It "Validate error from Compress-Archive when duplicate paths are supplied as input to LiteralPath parameter" { + $sourcePath = @( + "TestDrive:/SourceDir/Sample-1.txt", + "TestDrive:/SourceDir/Sample-1.txt") + $destinationPath = "TestDrive:/DuplicatePaths.zip" + + try + { + Compress-Archive -LiteralPath $sourcePath -DestinationPath $destinationPath + throw "Failed to detect that duplicate Path $sourcePath is supplied as input to LiteralPath parameter." + } + catch + { + $_.FullyQualifiedErrorId | Should -Be "DuplicatePaths,Microsoft.PowerShell.Archive.CompressArchiveCommand" + } + } + + ## From 504 + It "Validate that Source Path can be at SystemDrive location" -Skip { + $sourcePath = "$env:SystemDrive/SourceDir" + $destinationPath = "TestDrive:/SampleFromSystemDrive.zip" + New-Item $sourcePath -Type Directory | Out-Null # not enough permissions to write to drive root on Linux + "Some Data" | Out-File -FilePath $sourcePath/SampleSourceFileForArchive.txt + try + { + Compress-Archive -Path $sourcePath -DestinationPath $destinationPath + Test-Path $destinationPath | Should -Be $true + } + finally + { + Remove-Item "$sourcePath" -Force -Recurse -ErrorAction SilentlyContinue + } + } + + # This cannot happen in -WriteMode Create because another error will be throw before + It "Throws an error when Path and DestinationPath are the same" -Skip { + $sourcePath = "TestDrive:/SourceDir/Sample-1.txt" + $destinationPath = $sourcePath + + try { + # Note the cmdlet performs validation on $destinationPath + Compress-Archive -Path $sourcePath -DestinationPath $destinationPath + throw "Failed to detect an error when Path and DestinationPath are the same" + } catch { + $_.FullyQualifiedErrorId | Should -Be "SamePathAndDestinationPath,Microsoft.PowerShell.Archive.CompressArchiveCommand" + } + } + + It "Throws an error when Path and DestinationPath are the same and -Update is specified" { + $sourcePath = "TestDrive:/SourceDir/Sample-1.txt" + $destinationPath = $sourcePath + + try { + Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Update + throw "Failed to detect an error when Path and DestinationPath are the same and -Update is specified" + } catch { + $_.FullyQualifiedErrorId | Should -Be "SamePathAndDestinationPath,Microsoft.PowerShell.Archive.CompressArchiveCommand" + } + } + + It "Throws an error when Path and DestinationPath are the same and -Overwrite is specified" { + $sourcePath = "TestDrive:/EmptyDirectory" + $destinationPath = $sourcePath + + try { + Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Overwrite + throw "Failed to detect an error when Path and DestinationPath are the same and -Overwrite is specified" + } catch { + $_.FullyQualifiedErrorId | Should -Be "SamePathAndDestinationPath,Microsoft.PowerShell.Archive.CompressArchiveCommand" + } + } + + It "Throws an error when LiteralPath and DestinationPath are the same" -Skip { + $sourcePath = "TestDrive:/SourceDir/Sample-1.txt" + $destinationPath = $sourcePath + + try { + Compress-Archive -LiteralPath $sourcePath -DestinationPath $destinationPath + throw "Failed to detect an error when LiteralPath and DestinationPath are the same" + } catch { + $_.FullyQualifiedErrorId | Should -Be "SameLiteralPathAndDestinationPath,Microsoft.PowerShell.Archive.CompressArchiveCommand" + } + } + + It "Throws an error when LiteralPath and DestinationPath are the same and -Update is specified" { + $sourcePath = "TestDrive:/SourceDir/Sample-1.txt" + $destinationPath = $sourcePath + + try { + Compress-Archive -LiteralPath $sourcePath -DestinationPath $destinationPath -WriteMode Update + throw "Failed to detect an error when LiteralPath and DestinationPath are the same and -Update is specified" + } catch { + $_.FullyQualifiedErrorId | Should -Be "SameLiteralPathAndDestinationPath,Microsoft.PowerShell.Archive.CompressArchiveCommand" + } + } + + It "Throws an error when LiteralPath and DestinationPath are the same and -Overwrite is specified" { + $sourcePath = "TestDrive:/EmptyDirectory" + $destinationPath = $sourcePath + + try { + Compress-Archive -LiteralPath $sourcePath -DestinationPath $destinationPath -WriteMode Overwrite + throw "Failed to detect an error when LiteralPath and DestinationPath are the same and -Overwrite is specified" + } catch { + $_.FullyQualifiedErrorId | Should -Be "SameLiteralPathAndDestinationPath,Microsoft.PowerShell.Archive.CompressArchiveCommand" + } + } + } + + Context "WriteMode tests" { + BeforeAll { + New-Item TestDrive:/SourceDir -Type Directory | Out-Null + + $content = "Some Data" + $content | Out-File -FilePath TestDrive:/SourceDir/Sample-1.txt + } + + It "Throws a terminating error when an incorrect value is supplied to -WriteMode" { + $sourcePath = "TestDrive:/SourceDir" + $destinationPath = "TestDrive:/archive1.zip" + + try { + Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode mode + } catch { + $_.FullyQualifiedErrorId | Should -Be "CannotConvertArgumentNoMessage,Microsoft.PowerShell.Archive.CompressArchiveCommand" + } + } + + It "-WriteMode Create works" -Tag td1 { + $sourcePath = "TestDrive:/SourceDir" + $destinationPath = "TestDrive:/archive1.zip" + Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -Verbose + if ($IsWindows) { + $t = Convert-Path $destinationPath + 7z l "${t}" | Write-Verbose -Verbose + } + $destinationPath | Should -BeZipArchiveOnlyContaining @('SourceDir/', 'SourceDir/Sample-1.txt') + + + } + } + + Context "Basic functional tests" { + BeforeAll { + New-Item TestDrive:/SourceDir -Type Directory | Out-Null + New-Item TestDrive:/SourceDir/ChildDir-1 -Type Directory | Out-Null + New-Item TestDrive:/SourceDir/ChildDir-2 -Type Directory | Out-Null + New-Item TestDrive:/SourceDir/ChildEmptyDir -Type Directory | Out-Null + + # create an empty directory + New-Item TestDrive:/EmptyDir -Type Directory | Out-Null + + $content = "Some Data" + $content | Out-File -FilePath TestDrive:/SourceDir/Sample-1.txt + $content | Out-File -FilePath TestDrive:/SourceDir/ChildDir-1/Sample-2.txt + $content | Out-File -FilePath TestDrive:/SourceDir/ChildDir-2/Sample-3.txt + } + + It "Validate that a single file can be compressed" { + $sourcePath = "TestDrive:/SourceDir/ChildDir-1/Sample-2.txt" + $destinationPath = "TestDrive:/archive1.zip" + Compress-Archive -Path $sourcePath -DestinationPath $destinationPath + $destinationPath | Should -BeZipArchiveOnlyContaining @('Sample-2.txt') + } + + It "Validate that an empty folder can be compressed" { + $sourcePath = "TestDrive:/EmptyDir" + $destinationPath = "TestDrive:/archive2.zip" + Compress-Archive -Path $sourcePath -DestinationPath $destinationPath + $destinationPath | Should -BeZipArchiveOnlyContaining @('EmptyDir/') + } + + It "Validate a folder containing files, non-empty folders, and empty folders can be compressed" { + $sourcePath = "TestDrive:/SourceDir" + $destinationPath = "TestDrive:/archive3.zip" + Compress-Archive -Path $sourcePath -DestinationPath $destinationPath + $destinationPath | Should -BeZipArchiveOnlyContaining @('SourceDir/', 'SourceDir/ChildDir-1/', 'SourceDir/ChildDir-2/', 'SourceDir/ChildEmptyDir/', 'SourceDir/Sample-1.txt', 'SourceDir/ChildDir-1/Sample-2.txt', 'SourceDir/ChildDir-2/Sample-3.txt') + } + } + + Context "Update tests" -Skip { + + } + + Context "DestinationPath and -WriteMode Overwrite tests" { + BeforeAll { + New-Item TestDrive:/SourceDir -Type Directory | Out-Null + + $content = "Some Data" + $content | Out-File -FilePath TestDrive:/SourceDir/Sample-1.txt + + New-Item TestDrive:/archive3.zip -Type Directory | Out-Null + + New-Item TestDrive:/EmptyDirectory -Type Directory | Out-Null + + # Create a read-only archive + $readOnlyArchivePath = "TestDrive:/readonly.zip" + Compress-Archive -Path TestDrive:/SourceDir/Sample-1.txt -DestinationPath $readOnlyArchivePath + Set-ItemProperty -Path $readOnlyArchivePath -Name IsReadOnly -Value $true + + # Create TestDrive:/archive.zip + Compress-Archive -Path TestDrive:/SourceDir/Sample-1.txt -DestinationPath "TestDrive:/archive.zip" + + # Create Sample-2.txt + $content | Out-File -FilePath TestDrive:/Sample-2.txt + } + + It "Throws an error when archive file already exists and -Update and -Overwrite parameters are not specified" { + $sourcePath = "TestDrive:/SourceDir" + $destinationPath = "TestDrive:/archive1.zip" + + try + { + "Some Data" > $destinationPath + Compress-Archive -Path $sourcePath -DestinationPath $destinationPath + throw "Failed to validate that an archive file format $destinationPath already exists and -Update switch parameter is not specified." + } + catch + { + $_.FullyQualifiedErrorId | Should -Be "ArchiveExists,Microsoft.PowerShell.Archive.CompressArchiveCommand" + } + } + + It "Throws a terminating error when archive file exists and -Update is specified but the archive is read-only" { + $sourcePath = "TestDrive:/SourceDir" + $destinationPath = "TestDrive:/readonly.zip" + + try + { + Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Update + throw "Failed to detect an that an error was thrown when archive $destinationPath already exists but it is read-only and -WriteMode Update is specified." + } + catch + { + $_.FullyQualifiedErrorId | Should -Be "ArchiveReadOnly,Microsoft.PowerShell.Archive.CompressArchiveCommand" + } + } + + It "Throws a terminating error when archive already exists as a directory and -Update and -Overwrite parameters are not specified" { + $sourcePath = "TestDrive:/SourceDir/Sample-1.txt" + $destinationPath = "TestDrive:/SourceDir" + + try + { + Compress-Archive -Path $sourcePath -DestinationPath $destinationPath + throw "Failed to detect an error was thrown when archive $destinationPath exists as a directory and -WriteMode Update or -WriteMode Overwrite is not specified." + } + catch + { + $_.FullyQualifiedErrorId | Should -Be "ArchiveExistsAsDirectory,Microsoft.PowerShell.Archive.CompressArchiveCommand" + } + } + + It "Throws a terminating error when DestinationPath is a directory and -Update is specified" { + $sourcePath = "TestDrive:/SourceDir" + $destinationPath = "TestDrive:/archive3.zip" + + try + { + Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Update + throw "Failed to validate that a directory $destinationPath exists and -Update switch parameter is specified." + } + catch + { + $_.FullyQualifiedErrorId | Should -Be "ArchiveExistsAsDirectory,Microsoft.PowerShell.Archive.CompressArchiveCommand" + } + } + + It "Throws a terminating error when DestinationPath is a folder containing at least 1 item and Overwrite is specified" { + $sourcePath = "TestDrive:/SourceDir" + $destinationPath = "TestDrive:" + + try + { + Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Overwrite + throw "Failed to detect an error when $destinationPath is an existing directory containing at least 1 item and -Overwrite switch parameter is specified." + } + catch + { + $_.FullyQualifiedErrorId | Should -Be "ArchiveIsNonEmptyDirectory,Microsoft.PowerShell.Archive.CompressArchiveCommand" + } + } + + It "Throws a terminating error when archive does not exist and -Update mode is specified" { + $sourcePath = "TestDrive:/SourceDir" + $destinationPath = "TestDrive:/archive2.zip" + + try + { + Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Update + throw "Failed to validate that an archive file format $destinationPath does not exist and -Update switch parameter is specified." + } + catch + { + $_.FullyQualifiedErrorId | Should -Be "ArchiveDoesNotExist,Microsoft.PowerShell.Archive.CompressArchiveCommand" + } + } + + ## Overwrite tests + It "Throws an error when trying to overwrite an empty directory, which is the working directory" { + $sourcePath = "TestDrive:/Sample-2.txt" + $destinationPath = "TestDrive:/EmptyDirectory" + + Push-Location $destinationPath + + try { + Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Overwrite + } catch { + $_.FullyQualifiedErrorId | Should -Be "CannotOverwriteWorkingDirectory,Microsoft.PowerShell.Archive.CompressArchiveCommand" + } + + Pop-Location + } + + It "Overwrites a directory containing no items when -Overwrite is specified" { + $sourcePath = "TestDrive:/SourceDir" + $destinationPath = "TestDrive:/EmptyDirectory" + + # Ensure $destinationPath is a directory + Test-Path $destinationPath -PathType Container | Should -Be $true + + Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Overwrite + + # Ensure $destinationPath is now a file + Test-Path $destinationPath -PathType Leaf | Should -Be $true + } + + It "Overwrites an archive that already exists" { + $destinationPath = "TestDrive:/archive.zip" + + # Ensure the original archive contains Sample-1.txt + $destinationPath | Should -BeZipArchiveOnlyContaining @("Sample-1.txt") + + # Overwrite the archive + $sourcePath = "TestDrive:/Sample-2.txt" + Compress-Archive -Path $sourcePath -DestinationPath "TestDrive:/archive.zip" -WriteMode Overwrite + + # Ensure the original entries and different than the new entries + $destinationPath | Should -BeZipArchiveOnlyContaining @("Sample-2.txt") + } + } + + Context "Relative Path tests" { + BeforeAll { + New-Item TestDrive:/SourceDir -Type Directory | Out-Null + New-Item TestDrive:/SourceDir/ChildDir-1 -Type Directory | Out-Null + + $content = "Some Data" + $content | Out-File -FilePath TestDrive:/SourceDir/Sample-1.txt + $content | Out-File -FilePath TestDrive:/SourceDir/ChildDir-1/Sample-2.txt + } + + # From 568 + It "Validate that relative path can be specified as Path parameter of Compress-Archive cmdlet" { + $sourcePath = "./SourceDir" + $destinationPath = "RelativePathForPathParameter.zip" + try + { + Push-Location TestDrive:/ + Compress-Archive -Path $sourcePath -DestinationPath $destinationPath + Test-Path $destinationPath | Should -Be $true + } + finally + { + Pop-Location + } + } + + # From 582 + It "Validate that relative path can be specified as LiteralPath parameter of Compress-Archive cmdlet" { + $sourcePath = "./SourceDir" + $destinationPath = "RelativePathForLiteralPathParameter.zip" + try + { + Push-Location TestDrive:/ + Compress-Archive -LiteralPath $sourcePath -DestinationPath $destinationPath + Test-Path $destinationPath | Should -Be $true + } + finally + { + Pop-Location + } + } + + # From 596 + It "Validate that relative path can be specified as DestinationPath parameter of Compress-Archive cmdlet" -Tag this3 { + $sourcePath = "TestDrive:/SourceDir" + $destinationPath = "./RelativePathForDestinationPathParameter.zip" + try + { + Push-Location TestDrive:/ + Compress-Archive -Path $sourcePath -DestinationPath $destinationPath + Test-Path $destinationPath | Should -Be $true + } + finally + { + Pop-Location + } + } + } + + Context "Special and Wildcard Characters Tests" { + BeforeAll { + New-Item TestDrive:/SourceDir -Type Directory | Out-Null + + $content = "Some Data" + $content | Out-File -FilePath TestDrive:/SourceDir/Sample-1.txt + } + + + It "Accepts DestinationPath parameter with wildcard characters that resolves to one path" { + $sourcePath = "TestDrive:/SourceDir/Sample-1.txt" + $destinationPath = "TestDrive:/Sample[]SingleFile.zip" + Compress-Archive -Path $sourcePath -DestinationPath $destinationPath + Test-Path -LiteralPath $destinationPath | Should -Be $true + Remove-Item -LiteralPath $destinationPath + } + + It "Accepts DestinationPath parameter with [ but no matching ]" { + $sourcePath = "TestDrive:/SourceDir" + $destinationPath = "TestDrive:/archive[2.zip" + + Compress-Archive -Path $sourcePath -DestinationPath $destinationPath + $destinationPath | Should -BeZipArchiveOnlyContaining @("SourceDir/", "SourceDir/Sample-1.txt") -LiteralPath + Remove-Item -LiteralPath $destinationPath + } + } + + Context "test" -Tag lol { + BeforeAll { + $content = "Some Data" + $content | Out-File -FilePath TestDrive:/Sample-1.txt + Compress-Archive -Path TestDrive:/Sample-1.txt -DestinationPath TestDrive:/archive1.zip + } + + It "test custom assetion" { + "${TestDrive}/archive1.zip" | Should -BeZipArchiveOnlyContaining @("Sample-1.txt") + } + } +} diff --git a/Tests/Pester.Commands.Cmdlets.Archive.Tests.ps1 b/Tests/Pester.Commands.Cmdlets.Archive.Tests.ps1 deleted file mode 100644 index 7c3c619..0000000 --- a/Tests/Pester.Commands.Cmdlets.Archive.Tests.ps1 +++ /dev/null @@ -1,1258 +0,0 @@ -<############################################################################################ - # File: Pester.Commands.Cmdlets.ArchiveTests.ps1 - # Commands.Cmdlets.ArchiveTests suite contains Tests that are - # used for validating Microsoft.PowerShell.Archive module. - ############################################################################################> -$script:TestSourceRoot = $PSScriptRoot -$DS = [System.IO.Path]::DirectorySeparatorChar -if ($IsWindows -eq $null) { - $IsWindows = $PSVersionTable.PSEdition -eq "Desktop" -} -Describe "Test suite for Microsoft.PowerShell.Archive module" -Tags "BVT" { - - BeforeAll { - $originalProgressPref = $ProgressPreference - $ProgressPreference = "SilentlyContinue" - $originalPSModulePath = $env:PSModulePath - # make sure we use the one in this repo - $env:PSModulePath = "$($script:TestSourceRoot)\..;$($env:PSModulePath)" - - New-Item $TestDrive$($DS)SourceDir -Type Directory | Out-Null - New-Item $TestDrive$($DS)SourceDir$($DS)ChildDir-1 -Type Directory | Out-Null - New-Item $TestDrive$($DS)SourceDir$($DS)ChildDir-2 -Type Directory | Out-Null - New-Item $TestDrive$($DS)SourceDir$($DS)ChildEmptyDir -Type Directory | Out-Null - - $content = "Some Data" - $content | Out-File -FilePath $TestDrive$($DS)SourceDir$($DS)Sample-1.txt - $content | Out-File -FilePath $TestDrive$($DS)SourceDir$($DS)Sample-2.txt - $content | Out-File -FilePath $TestDrive$($DS)SourceDir$($DS)ChildDir-1$($DS)Sample-3.txt - $content | Out-File -FilePath $TestDrive$($DS)SourceDir$($DS)ChildDir-1$($DS)Sample-4.txt - $content | Out-File -FilePath $TestDrive$($DS)SourceDir$($DS)ChildDir-2$($DS)Sample-5.txt - $content | Out-File -FilePath $TestDrive$($DS)SourceDir$($DS)ChildDir-2$($DS)Sample-6.txt - - "Some Text" > $TestDrive$($DS)Sample.unzip - "Some Text" > $TestDrive$($DS)Sample.cab - - $preCreatedArchivePath = Join-Path $script:TestSourceRoot "SamplePreCreatedArchive.archive" - Copy-Item $preCreatedArchivePath $TestDrive$($DS)SamplePreCreatedArchive.zip -Force - - $preCreatedArchivePath = Join-Path $script:TestSourceRoot "TrailingSpacer.archive" - Copy-Item $preCreatedArchivePath $TestDrive$($DS)TrailingSpacer.zip -Force - } - - AfterAll { - $global:ProgressPreference = $originalProgressPref - $env:PSModulePath = $originalPSModulePath - } - - function Add-CompressionAssemblies { - Add-Type -AssemblyName System.IO.Compression - if ($psedition -eq "Core") - { - Add-Type -AssemblyName System.IO.Compression.ZipFile - } - else - { - Add-Type -AssemblyName System.IO.Compression.FileSystem - } - } - - function CompressArchivePathParameterSetValidator { - param - ( - [string[]] $path, - [string] $destinationPath, - [string] $compressionLevel = "Optimal" - ) - - try - { - Compress-Archive -Path $path -DestinationPath $destinationPath -CompressionLevel $compressionLevel - throw "ValidateNotNullOrEmpty attribute is missing on one of parameters belonging to Path parameterset." - } - catch - { - $_.FullyQualifiedErrorId | Should Be "ParameterArgumentValidationError,Compress-Archive" - } - } - - function CompressArchiveLiteralPathParameterSetValidator { - param - ( - [string[]] $literalPath, - [string] $destinationPath, - [string] $compressionLevel = "Optimal" - ) - - try - { - Compress-Archive -LiteralPath $literalPath -DestinationPath $destinationPath -CompressionLevel $compressionLevel - throw "ValidateNotNullOrEmpty attribute is missing on one of parameters belonging to LiteralPath parameterset." - } - catch - { - $_.FullyQualifiedErrorId | Should Be "ParameterArgumentValidationError,Compress-Archive" - } - } - - - function CompressArchiveInValidPathValidator { - param - ( - [string[]] $path, - [string] $destinationPath, - [string] $invalidPath, - [string] $expectedFullyQualifiedErrorId - ) - - try - { - Compress-Archive -Path $path -DestinationPath $destinationPath - throw "Failed to validate that an invalid Path $invalidPath was supplied as input to Compress-Archive cmdlet." - } - catch - { - $_.FullyQualifiedErrorId | Should Be $expectedFullyQualifiedErrorId - } - } - - function CompressArchiveInValidArchiveFileExtensionValidator { - param - ( - [string[]] $path, - [string] $destinationPath, - [string] $invalidArchiveFileExtension - ) - - try - { - Compress-Archive -Path $path -DestinationPath $destinationPath - throw "Failed to validate that an invalid archive file format $invalidArchiveFileExtension was supplied as input to Compress-Archive cmdlet." - } - catch - { - $_.FullyQualifiedErrorId | Should Be "NotSupportedArchiveFileExtension,Compress-Archive" - } - } - - function Validate-ArchiveEntryCount { - param - ( - [string] $path, - [int] $expectedEntryCount - ) - - Add-CompressionAssemblies - try - { - $archiveFileStreamArgs = @($path, [System.IO.FileMode]::Open) - $archiveFileStream = New-Object -TypeName System.IO.FileStream -ArgumentList $archiveFileStreamArgs - - $zipArchiveArgs = @($archiveFileStream, [System.IO.Compression.ZipArchiveMode]::Read, $false) - $zipArchive = New-Object -TypeName System.IO.Compression.ZipArchive -ArgumentList $zipArchiveArgs - - $actualEntryCount = $zipArchive.Entries.Count - $actualEntryCount | Should Be $expectedEntryCount - } - finally - { - if ($null -ne $zipArchive) { $zipArchive.Dispose()} - if ($null -ne $archiveFileStream) { $archiveFileStream.Dispose() } - } - } - - function ArchiveFileEntryContentValidator { - param - ( - [string] $path, - [string] $entryFileName, - [string] $expectedEntryFileContent - ) - - Add-CompressionAssemblies - try - { - $destFile = "$TestDrive$($DS)ExpandedFile"+([System.Guid]::NewGuid().ToString())+".txt" - - $archiveFileStreamArgs = @($path, [System.IO.FileMode]::Open) - $archiveFileStream = New-Object -TypeName System.IO.FileStream -ArgumentList $archiveFileStreamArgs - - $zipArchiveArgs = @($archiveFileStream, [System.IO.Compression.ZipArchiveMode]::Read, $false) - $zipArchive = New-Object -TypeName System.IO.Compression.ZipArchive -ArgumentList $zipArchiveArgs - - $entryToBeUpdated = $zipArchive.Entries | ? {$_.FullName -eq $entryFileName.replace([System.IO.Path]::DirectorySeparatorChar, [System.IO.Path]::AltDirectorySeparatorChar)} - - if($entryToBeUpdated -ne $null) - { - $srcStream = $entryToBeUpdated.Open() - $destStream = New-Object "System.IO.FileStream" -ArgumentList( $destFile, [System.IO.FileMode]::Create ) - $srcStream.CopyTo( $destStream ) - $destStream.Dispose() - $srcStream.Dispose() - Get-Content $destFile | Should Be $expectedEntryFileContent - } - else - { - throw "Failed to find the file $entryFileName in the archive file $path" - } - } - finally - { - if ($zipArchive) - { - $zipArchive.Dispose() - } - if ($archiveFileStream) - { - $archiveFileStream.Dispose() - } - } - } - - function ArchiveFileEntrySeparatorValidator { - param - ( - [string] $path - ) - - Add-CompressionAssemblies - try - { - $destFile = "$TestDrive$($DS)ExpandedFile"+([System.Guid]::NewGuid().ToString())+".txt" - - $archiveFileStreamArgs = @($path, [System.IO.FileMode]::Open) - $archiveFileStream = New-Object -TypeName System.IO.FileStream -ArgumentList $archiveFileStreamArgs - - $zipArchiveArgs = @($archiveFileStream, [System.IO.Compression.ZipArchiveMode]::Read, $false) - $zipArchive = New-Object -TypeName System.IO.Compression.ZipArchive -ArgumentList $zipArchiveArgs - - $badEntries = $zipArchive.Entries | Where-Object {$_.FullName.Contains('\')} - - $badEntries.Count | Should Be 0 - } - finally - { - if ($zipArchive) - { - $zipArchive.Dispose() - } - if ($archiveFileStream) - { - $archiveFileStream.Dispose() - } - } - } - - function ExpandArchiveInvalidParameterValidator { - param - ( - [boolean] $isLiteralPathParameterSet, - [string[]] $path, - [string] $destinationPath, - [string] $expectedFullyQualifiedErrorId - ) - - try - { - if($isLiteralPathParameterSet) - { - Expand-Archive -LiteralPath $literalPath -DestinationPath $destinationPath - } - else - { - Expand-Archive -Path $path -DestinationPath $destinationPath - } - - throw "Expand-Archive did NOT throw expected error" - } - catch - { - $_.FullyQualifiedErrorId | Should Be $expectedFullyQualifiedErrorId - } - } - - Context "Compress-Archive - Parameter validation test cases" { - - It "Validate errors from Compress-Archive with NULL & EMPTY values for Path, LiteralPath, DestinationPath, CompressionLevel parameters" { - $sourcePath = "$TestDrive$($DS)SourceDir" - $destinationPath = "$TestDrive$($DS)SampleSingleFile.zip" - - CompressArchivePathParameterSetValidator $null $destinationPath - CompressArchivePathParameterSetValidator $sourcePath $null - CompressArchivePathParameterSetValidator $null $null - - CompressArchivePathParameterSetValidator "" $destinationPath - CompressArchivePathParameterSetValidator $sourcePath "" - CompressArchivePathParameterSetValidator "" "" - - CompressArchivePathParameterSetValidator $null $null "NoCompression" - - CompressArchiveLiteralPathParameterSetValidator $null $destinationPath - CompressArchiveLiteralPathParameterSetValidator $sourcePath $null - CompressArchiveLiteralPathParameterSetValidator $null $null - - CompressArchiveLiteralPathParameterSetValidator "" $destinationPath - CompressArchiveLiteralPathParameterSetValidator $sourcePath "" - CompressArchiveLiteralPathParameterSetValidator "" "" - - CompressArchiveLiteralPathParameterSetValidator $null $null "NoCompression" - - CompressArchiveLiteralPathParameterSetValidator $sourcePath $destinationPath $null - CompressArchiveLiteralPathParameterSetValidator $sourcePath $destinationPath "" - } - - It "Validate errors from Compress-Archive when invalid path (non-existing path / non-filesystem path) is supplied for Path or LiteralPath parameters" { - CompressArchiveInValidPathValidator "$TestDrive$($DS)InvalidPath" $TestDrive "$TestDrive$($DS)InvalidPath" "ArchiveCmdletPathNotFound,Compress-Archive" - CompressArchiveInValidPathValidator "$TestDrive" "$TestDrive$($DS)NonExistingDirectory$($DS)sample.zip" "$TestDrive$($DS)NonExistingDirectory$($DS)sample.zip" "ArchiveCmdletPathNotFound,Compress-Archive" - - $path = @("$TestDrive", "$TestDrive$($DS)InvalidPath") - CompressArchiveInValidPathValidator $path $TestDrive "$TestDrive$($DS)InvalidPath" "ArchiveCmdletPathNotFound,Compress-Archive" - - # The tests below are no longer valid. You can have zip files with non-zip extensions. Different archive - # formats should be added in a separate pull request, with a parameter to identify the archive format, and - # default formats associated with specific extensions. Until then, as long as these cmdlets only support - # Zip files, any file extension is supported. - - #$invalidUnZipFileFormat = "$TestDrive$($DS)Sample.unzip" - #CompressArchiveInValidArchiveFileExtensionValidator $TestDrive "$invalidUnZipFileFormat" ".unzip" - - #$invalidcabZipFileFormat = "$TestDrive$($DS)Sample.cab" - #CompressArchiveInValidArchiveFileExtensionValidator $TestDrive "$invalidcabZipFileFormat" ".cab" - } - - It "Validate error from Compress-Archive when archive file already exists and -Update parameter is not specified" { - $sourcePath = "$TestDrive$($DS)SourceDir" - $destinationPath = "$TestDrive$($DS)ValidateErrorWhenUpdateNotSpecified.zip" - - try - { - "Some Data" > $destinationPath - Compress-Archive -Path $sourcePath -DestinationPath $destinationPath - throw "Failed to validate that an archive file format $destinationPath already exists and -Update switch parameter is not specified while running Compress-Archive command." - } - catch - { - $_.FullyQualifiedErrorId | Should Be "ArchiveFileExists,Compress-Archive" - } - } - - It "Validate error from Compress-Archive when duplicate paths are supplied as input to Path parameter" { - $sourcePath = @( - "$TestDrive$($DS)SourceDir$($DS)Sample-1.txt", - "$TestDrive$($DS)SourceDir$($DS)Sample-1.txt") - $destinationPath = "$TestDrive$($DS)DuplicatePaths.zip" - - try - { - Compress-Archive -Path $sourcePath -DestinationPath $destinationPath - throw "Failed to detect that duplicate Path $sourcePath is supplied as input to Path parameter." - } - catch - { - $_.FullyQualifiedErrorId | Should Be "DuplicatePathFound,Compress-Archive" - } - } - - It "Validate error from Compress-Archive when duplicate paths are supplied as input to LiteralPath parameter" { - $sourcePath = @( - "$TestDrive$($DS)SourceDir$($DS)Sample-1.txt", - "$TestDrive$($DS)SourceDir$($DS)Sample-1.txt") - $destinationPath = "$TestDrive$($DS)DuplicatePaths.zip" - - try - { - Compress-Archive -LiteralPath $sourcePath -DestinationPath $destinationPath - throw "Failed to detect that duplicate Path $sourcePath is supplied as input to LiteralPath parameter." - } - catch - { - $_.FullyQualifiedErrorId | Should Be "DuplicatePathFound,Compress-Archive" - } - } - } - - Context "Compress-Archive - functional test cases" { - It "Validate that a single file can be compressed using Compress-Archive cmdlet" { - $sourcePath = "$TestDrive$($DS)SourceDir$($DS)ChildDir-1$($DS)Sample-3.txt" - $destinationPath = "$TestDrive$($DS)SampleSingleFile.zip" - Compress-Archive -Path $sourcePath -DestinationPath $destinationPath - $destinationPath | Should Exist - } - # This test requires a fix in PS5 to support reading paths with square bracket - It "Validate that Compress-Archive cmdlet can accept LiteralPath parameter with Special Characters" -skip:(($PSVersionTable.psversion.Major -lt 5) -and ($PSVersionTable.psversion.Minor -lt 0)) { - $sourcePath = "$TestDrive$($DS)SourceDir$($DS)ChildDir-1$($DS)Sample[]File.txt" - "Some Random Content" | Out-File -LiteralPath $sourcePath - $destinationPath = "$TestDrive$($DS)SampleSingleFileWithSpecialCharacters.zip" - try - { - Compress-Archive -LiteralPath $sourcePath -DestinationPath $destinationPath - $destinationPath | Should Exist - } - finally - { - Remove-Item -LiteralPath $sourcePath -Force - } - } - It "Validate that Compress-Archive cmdlet errors out when DestinationPath resolves to multiple locations" { - - New-Item $TestDrive$($DS)SampleDir$($DS)Child-1 -Type Directory -Force | Out-Null - New-Item $TestDrive$($DS)SampleDir$($DS)Child-2 -Type Directory -Force | Out-Null - New-Item $TestDrive$($DS)SampleDir$($DS)Test.txt -Type File -Force | Out-Null - - $destinationPath = "$TestDrive$($DS)SampleDir$($DS)Child-*$($DS)SampleChidArchive.zip" - $sourcePath = "$TestDrive$($DS)SampleDir$($DS)Test.txt" - try - { - Compress-Archive -Path $sourcePath -DestinationPath $destinationPath - throw "Failed to detect that destination $destinationPath can resolve to multiple paths" - } - catch - { - $_.FullyQualifiedErrorId | Should Be "InvalidArchiveFilePath,Compress-Archive" - } - finally - { - Remove-Item -LiteralPath $TestDrive$($DS)SampleDir -Force -Recurse - } - } - It "Validate that Compress-Archive cmdlet works when DestinationPath has wild card pattern and resolves to a single valid path" { - - New-Item $TestDrive$($DS)SampleDir$($DS)Child-1 -Type Directory -Force | Out-Null - New-Item $TestDrive$($DS)SampleDir$($DS)Test.txt -Type File -Force | Out-Null - - $destinationPath = "$TestDrive$($DS)SampleDir$($DS)Child-*$($DS)SampleChidArchive.zip" - $sourcePath = "$TestDrive$($DS)SampleDir$($DS)Test.txt" - try - { - Compress-Archive -Path $sourcePath -DestinationPath $destinationPath - $destinationPath | Should Exist - } - finally - { - Remove-Item -LiteralPath $TestDrive$($DS)SampleDir -Force -Recurse - } - } - It "Validate that Compress-Archive cmdlet works when it ecounters LastWriteTimeValues earlier than 1980" { - New-Item $TestDrive$($DS)SampleDir$($DS)Child-1 -Type Directory -Force | Out-Null - $file = New-Item $TestDrive$($DS)SampleDir$($DS)Test.txt -Type File -Force - $destinationPath = "$TestDrive$($DS)SampleDir$($DS)Child-*$($DS)SampleChidArchive.zip" - $sourcePath = "$TestDrive$($DS)SampleDir$($DS)Test.txt" - - $file.LastWriteTime = [DateTime]::Parse('1967-03-04T06:00:00') - try - { - Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -WarningAction SilentlyContinue - $destinationPath | Should Exist - } - finally - { - Remove-Item -LiteralPath $TestDrive$($DS)SampleDir -Force -Recurse - } - } - It "Validate that Compress-Archive cmdlet warns when updating the LastWriteTime for files earlier than 1980" { - New-Item $TestDrive$($DS)SampleDir$($DS)Child-1 -Type Directory -Force | Out-Null - $file = New-Item $TestDrive$($DS)SampleDir$($DS)Test.txt -Type File -Force - $destinationPath = "$TestDrive$($DS)SampleDir$($DS)Child-*$($DS)SampleChidArchive.zip" - $sourcePath = "$TestDrive$($DS)SampleDir$($DS)Test.txt" - - $file.LastWriteTime = [DateTime]::Parse('1967-03-04T06:00:00') - try - { - $ps=[PowerShell]::Create() - $ps.Streams.Warning.Clear() - $script = "Import-Module Microsoft.PowerShell.Archive; Compress-Archive -Path $sourcePath -DestinationPath `"$destinationPath`" -CompressionLevel Fastest -Verbose" - $ps.AddScript($script) - $ps.Invoke() - - $ps.Streams.Warning.Count -gt 0 | Should Be $True - } - finally - { - Remove-Item -LiteralPath $TestDrive$($DS)SampleDir -Force -Recurse - } - } - - # This test requires a fix in PS5 to support reading paths with square bracket - It "Validate that Compress-Archive cmdlet can accept LiteralPath parameter for a directory with Special Characters in the directory name" -skip:(($PSVersionTable.psversion.Major -lt 5) -and ($PSVersionTable.psversion.Minor -lt 0)) { - $sourcePath = "$TestDrive$($DS)Source[]Dir$($DS)ChildDir[]-1" - New-Item $sourcePath -Type Directory | Out-Null - "Some Random Content" | Out-File -LiteralPath "$sourcePath$($DS)Sample[]File.txt" - $destinationPath = "$TestDrive$($DS)SampleDirWithSpecialCharacters.zip" - try - { - Compress-Archive -LiteralPath $sourcePath -DestinationPath $destinationPath - $destinationPath | Should Exist - } - finally - { - Remove-Item -LiteralPath $sourcePath -Force -Recurse - } - } - It "Validate that Compress-Archive cmdlet can accept DestinationPath parameter with Special Characters" { - $sourcePath = "$TestDrive$($DS)SourceDir$($DS)ChildDir-1$($DS)Sample-3.txt" - $destinationPath = "$TestDrive$($DS)Sample[]SingleFile.zip" - try - { - Compress-Archive -Path $sourcePath -DestinationPath $destinationPath - Test-Path -LiteralPath $destinationPath | Should Be $true - } - finally - { - Remove-Item -LiteralPath $destinationPath -Force - } - } - It "Validate that Source Path can be at SystemDrive location" -skip:(!$IsWindows) { - $sourcePath = "$env:SystemDrive$($DS)SourceDir" - $destinationPath = "$TestDrive$($DS)SampleFromSystemDrive.zip" - New-Item $sourcePath -Type Directory | Out-Null # not enough permissions to write to drive root on Linux - "Some Data" | Out-File -FilePath $sourcePath$($DS)SampleSourceFileForArchive.txt - try - { - Compress-Archive -Path $sourcePath -DestinationPath $destinationPath - Test-Path $destinationPath | Should Be $true - } - finally - { - del "$sourcePath" -Force -Recurse -ErrorAction SilentlyContinue - } - } - It "Validate that multiple files can be compressed using Compress-Archive cmdlet" { - $sourcePath = @( - "$TestDrive$($DS)SourceDir$($DS)ChildDir-1$($DS)Sample-3.txt", - "$TestDrive$($DS)SourceDir$($DS)ChildDir-1$($DS)Sample-4.txt", - "$TestDrive$($DS)SourceDir$($DS)ChildDir-2$($DS)Sample-5.txt", - "$TestDrive$($DS)SourceDir$($DS)ChildDir-2$($DS)Sample-6.txt") - $destinationPath = "$TestDrive$($DS)SampleMultipleFiles.zip" - Compress-Archive -Path $sourcePath -DestinationPath $destinationPath - Test-Path $destinationPath | Should Be $true - } - It "Validate that multiple files and directories can be compressed using Compress-Archive cmdlet" { - $sourcePath = @( - "$TestDrive$($DS)SourceDir$($DS)Sample-1.txt", - "$TestDrive$($DS)SourceDir$($DS)Sample-2.txt", - "$TestDrive$($DS)SourceDir$($DS)ChildDir-1", - "$TestDrive$($DS)SourceDir$($DS)ChildDir-2") - $destinationPath = "$TestDrive$($DS)SampleMultipleFilesAndDirs.zip" - Compress-Archive -Path $sourcePath -DestinationPath $destinationPath - Test-Path $destinationPath | Should Be $true - } - It "Validate that a single directory can be compressed using Compress-Archive cmdlet" { - $sourcePath = @("$TestDrive$($DS)SourceDir$($DS)ChildDir-1") - $destinationPath = "$TestDrive$($DS)SampleSingleDir.zip" - Compress-Archive -Path $sourcePath -DestinationPath $destinationPath - Test-Path $destinationPath | Should Be $true - } - It "Validate that a single directory with multiple files and subdirectories can be compressed using Compress-Archive cmdlet" { - $sourcePath = @("$TestDrive$($DS)SourceDir") - $destinationPath = "$TestDrive$($DS)SampleSubTree.zip" - Compress-Archive -Path $sourcePath -DestinationPath $destinationPath - Test-Path $destinationPath | Should Be $true - } - It "Validate that a single directory & multiple files can be compressed using Compress-Archive cmdlet" { - $sourcePath = @( - "$TestDrive$($DS)SourceDir$($DS)ChildDir-1", - "$TestDrive$($DS)SourceDir$($DS)Sample-1.txt", - "$TestDrive$($DS)SourceDir$($DS)Sample-2.txt") - $destinationPath = "$TestDrive$($DS)SampleMultipleFilesAndSingleDir.zip" - Compress-Archive -Path $sourcePath -DestinationPath $destinationPath - Test-Path $destinationPath | Should Be $true - } - - It "Validate that if .zip extension is not supplied as input to DestinationPath parameter, then .zip extension is appended" { - $sourcePath = @("$TestDrive$($DS)SourceDir") - $destinationPath = "$TestDrive$($DS)SampleNoExtension.zip" - $destinationWithoutExtensionPath = "$TestDrive$($DS)SampleNoExtension" - Compress-Archive -Path $sourcePath -DestinationPath $destinationWithoutExtensionPath - Test-Path $destinationPath | Should Be $true - } - It "Validate that relative path can be specified as Path parameter of Compress-Archive cmdlet" { - $sourcePath = ".$($DS)SourceDir" - $destinationPath = "RelativePathForPathParameter.zip" - try - { - Push-Location $TestDrive - Compress-Archive -Path $sourcePath -DestinationPath $destinationPath - Test-Path $destinationPath | Should Be $true - } - finally - { - Pop-Location - } - } - It "Validate that relative path can be specified as LiteralPath parameter of Compress-Archive cmdlet" { - $sourcePath = ".$($DS)SourceDir" - $destinationPath = "RelativePathForLiteralPathParameter.zip" - try - { - Push-Location $TestDrive - Compress-Archive -LiteralPath $sourcePath -DestinationPath $destinationPath - Test-Path $destinationPath | Should Be $true - } - finally - { - Pop-Location - } - } - It "Validate that relative path can be specified as DestinationPath parameter of Compress-Archive cmdlet" { - $sourcePath = "$TestDrive$($DS)SourceDir" - $destinationPath = ".$($DS)RelativePathForDestinationPathParameter.zip" - try - { - Push-Location $TestDrive - Compress-Archive -Path $sourcePath -DestinationPath $destinationPath - Test-Path $destinationPath | Should Be $true - } - finally - { - Pop-Location - } - } - It "Validate that -Update parameter makes Compress-Archive to not throw an error if archive file already exists" { - $sourcePath = @("$TestDrive$($DS)SourceDir") - $destinationPath = "$TestDrive$($DS)SampleUpdateTest.zip" - Compress-Archive -Path $sourcePath -DestinationPath $destinationPath - Test-Path $destinationPath | Should Be $true - Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -Update - Test-Path $destinationPath | Should Be $true - } - It "Validate -Update parameter by adding a new file to an existing archive file" { - $sourcePath = @("$TestDrive$($DS)SourceDir$($DS)ChildDir-1") - $destinationPath = "$TestDrive$($DS)SampleUpdateAdd1File.zip" - Compress-Archive -Path $sourcePath -DestinationPath $destinationPath - Test-Path $destinationPath | Should Be $true - New-Item $TestDrive$($DS)SourceDir$($DS)ChildDir-1$($DS)Sample-AddedNewFile.txt -Type File | Out-Null - Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -Update - Test-Path $destinationPath | Should Be $true - Validate-ArchiveEntryCount -path $destinationPath -expectedEntryCount 3 - } - - It "Validate that all CompressionLevel values can be used with Compress-Archive cmdlet" { - $sourcePath = "$TestDrive$($DS)SourceDir$($DS)Sample-1.txt" - - $destinationPath = "$TestDrive$($DS)FastestCompressionLevel.zip" - Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -CompressionLevel Fastest - Test-Path $destinationPath | Should Be $true - - $destinationPath = "$TestDrive$($DS)OptimalCompressionLevel.zip" - Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -CompressionLevel Optimal - Test-Path $destinationPath | Should Be $true - - $destinationPath = "$TestDrive$($DS)NoCompressionCompressionLevel.zip" - Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -CompressionLevel NoCompression - Test-Path $destinationPath | Should Be $true - } - - It "Validate that -Update parameter is modifying a file that already exists in the archive file" { - $filePath = "$TestDrive$($DS)SourceDir$($DS)ChildDir-1$($DS)Sample-3.txt" - - $initialContent = "Initial Content" - $modifiedContent = "Modified Content" - - $initialContent | Set-Content $filePath - - $sourcePath = "$TestDrive$($DS)SourceDir" - $destinationPath = "$TestDrive$($DS)UpdatingModifiedFile.zip" - - Compress-Archive -Path $sourcePath -DestinationPath $destinationPath - Test-Path $destinationPath | Should Be $True - - $modifiedContent | Set-Content $filePath - - Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -Update - Test-Path $destinationPath | Should Be $True - - ArchiveFileEntryContentValidator "$destinationPath" "SourceDir$($DS)ChildDir-1$($DS)Sample-3.txt" $modifiedContent - } - - It "Validate that only / separators are used as archive directory separators" { - $filePath = "$TestDrive$($DS)SourceDir$($DS)ChildDir-1$($DS)Sample-3.txt" - - $initialContent = "Initial Content" - $modifiedContent = "Modified Content" - - $initialContent | Set-Content $filePath - - $sourcePath = "$TestDrive$($DS)SourceDir" - $destinationPath = "$TestDrive$($DS)VerifyingSeparators.zip" - - Compress-Archive -Path $sourcePath -DestinationPath $destinationPath - Test-Path $destinationPath | Should Be $True - - ArchiveFileEntrySeparatorValidator "$destinationPath" - } - - It "Validate Compress-Archive cmdlet in pipleline scenario" { - $destinationPath = "$TestDrive$($DS)CompressArchiveFromPipeline.zip" - - # Piping a single file path to Compress-Archive - dir -Path $TestDrive$($DS)SourceDir$($DS)Sample-1.txt | Compress-Archive -DestinationPath $destinationPath - Test-Path $destinationPath | Should Be $True - - # Piping a string directory path to Compress-Archive - "$TestDrive$($DS)SourceDir$($DS)ChildDir-2" | Compress-Archive -DestinationPath $destinationPath -Update - Test-Path $destinationPath | Should Be $True - - # Piping the output of Get-ChildItem to Compress-Archive - dir "$TestDrive$($DS)SourceDir" -Recurse | Compress-Archive -DestinationPath $destinationPath -Update - Test-Path $destinationPath | Should Be $True - } - - It "Validate that Compress-Archive works on ReadOnly files" { - $sourcePath = "$TestDrive$($DS)ReadOnlyFile.txt" - $destinationPath = "$TestDrive$($DS)TestForReadOnlyFile.zip" - - "Some Content" | Out-File -FilePath $sourcePath - $createdItem = Get-Item $sourcePath - $createdItem.Attributes = 'ReadOnly' - - Compress-Archive -Path $sourcePath -DestinationPath $destinationPath - Test-Path $destinationPath | Should Be $true - } - - It "Validate that Compress-Archive generates Verbose messages" { - $sourcePath = "$TestDrive$($DS)SourceDir" - $destinationPath = "$TestDrive$($DS)Compress-Archive generates VerboseMessages.zip" - - try - { - $ps=[PowerShell]::Create() - $ps.Streams.Error.Clear() - $ps.Streams.Verbose.Clear() - $script = "Import-Module Microsoft.PowerShell.Archive; Compress-Archive -Path $sourcePath -DestinationPath `"$destinationPath`" -CompressionLevel Fastest -Verbose" - $ps.AddScript($script) - $ps.Invoke() - - $ps.Streams.Verbose.Count -gt 0 | Should Be $True - $ps.Streams.Error.Count | Should Be 0 - } - finally - { - $ps.Dispose() - } - } - - It "Validate that Compress-Archive returns nothing when -PassThru is not used" { - $sourcePath = @("$TestDrive$($DS)SourceDir") - $destinationPath = "$TestDrive$($DS)NoPassThruTest.zip" - $archive = Compress-Archive -Path $sourcePath -DestinationPath $destinationPath - $archive | Should Be $null - } - - It "Validate that Compress-Archive returns nothing when -PassThru is used with a value of $false" { - $sourcePath = @("$TestDrive$($DS)SourceDir") - $destinationPath = "$TestDrive$($DS)FalsePassThruTest.zip" - $archive = Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -PassThru:$false - $archive | Should Be $null - } - - It "Validate that Compress-Archive returns the archive when invoked with -PassThru" { - $sourcePath = @("$TestDrive$($DS)SourceDir") - $destinationPath = "$TestDrive$($DS)PassThruTest.zip" - $archive = Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -PassThru - $archive.FullName | Should Be $destinationPath - } - - It "Validate that Compress-Archive can create a zip archive that has a different extension" { - $sourcePath = "$TestDrive$($DS)SourceDir$($DS)ChildDir-1$($DS)Sample-3.txt" - $destinationPath = "$TestDrive$($DS)DifferentZipExtension.dat" - Compress-Archive -Path $sourcePath -DestinationPath $destinationPath - $destinationPath | Should Exist - } - - It "Validate that Compress-Archive can create a zip archive when the source is in use" { - $sourcePath = "$TestDrive$($DS)InUseFile.txt" - $destinationPath = "$TestDrive$($DS)TestForinUseFile.zip" - - "Some Content" | Out-File -FilePath $sourcePath - Get-Content $sourcePath - $TestFile = [System.IO.File]::Open($sourcePath, 'Open', 'Read', 'Read') - - try { - Compress-Archive -Path $sourcePath -DestinationPath $destinationPath - Test-Path $destinationPath | Should Be $true - } - finally { - $TestFile.Close() - } - } - } - - Context "Expand-Archive - Parameter validation test cases" { - It "Validate non existing archive -Path trows expected error message" { - $sourcePath = "$TestDrive$($DS)SourceDir" - $destinationPath = "$TestDrive$($DS)ExpandedArchive" - try - { - Expand-Archive -Path $sourcePath -DestinationPath $destinationPath - throw "Expand-Archive succeeded for non existing archive path" - } - catch - { - $_.FullyQualifiedErrorId | Should Be "PathNotFound,Expand-Archive" - } - } - - It "Validate errors from Expand-Archive with NULL & EMPTY values for Path, LiteralPath, DestinationPath parameters" { - ExpandArchiveInvalidParameterValidator $false $null "$TestDrive$($DS)SourceDir" "ParameterArgumentValidationError,Expand-Archive" - ExpandArchiveInvalidParameterValidator $false $null $null "ParameterArgumentValidationError,Expand-Archive" - - ExpandArchiveInvalidParameterValidator $false "$TestDrive$($DS)SourceDir" $null "ParameterArgumentTransformationError,Expand-Archive" - ExpandArchiveInvalidParameterValidator $false "" "$TestDrive$($DS)SourceDir" "ParameterArgumentTransformationError,Expand-Archive" - ExpandArchiveInvalidParameterValidator $false "$TestDrive$($DS)SourceDir" "" "ParameterArgumentTransformationError,Expand-Archive" - ExpandArchiveInvalidParameterValidator $false "" "" "ParameterArgumentTransformationError,Expand-Archive" - - ExpandArchiveInvalidParameterValidator $true $null "$TestDrive$($DS)SourceDir" "ParameterArgumentValidationError,Expand-Archive" - ExpandArchiveInvalidParameterValidator $true $null $null "ParameterArgumentValidationError,Expand-Archive" - - ExpandArchiveInvalidParameterValidator $true "$TestDrive$($DS)SourceDir" $null "ParameterArgumentValidationError,Expand-Archive" - ExpandArchiveInvalidParameterValidator $true "" "$TestDrive$($DS)SourceDir" "ParameterArgumentValidationError,Expand-Archive" - ExpandArchiveInvalidParameterValidator $true "$TestDrive$($DS)SourceDir" "" "ParameterArgumentValidationError,Expand-Archive" - ExpandArchiveInvalidParameterValidator $true "" "" "ParameterArgumentValidationError,Expand-Archive" - - ExpandArchiveInvalidParameterValidator $true $null "$TestDrive$($DS)SourceDir" "ParameterArgumentValidationError,Expand-Archive" - ExpandArchiveInvalidParameterValidator $true $null $null "ParameterArgumentValidationError,Expand-Archive" - - ExpandArchiveInvalidParameterValidator $true "$TestDrive$($DS)SourceDir" $null "ParameterArgumentValidationError,Expand-Archive" - ExpandArchiveInvalidParameterValidator $true "" "$TestDrive$($DS)SourceDir" "ParameterArgumentValidationError,Expand-Archive" - ExpandArchiveInvalidParameterValidator $true "$TestDrive$($DS)SourceDir" "" "ParameterArgumentValidationError,Expand-Archive" - ExpandArchiveInvalidParameterValidator $true "" "" "ParameterArgumentValidationError,Expand-Archive" - } - - It "Validate errors from Expand-Archive when invalid path (non-existing path / non-filesystem path) is supplied for Path or LiteralPath parameters" { - try { Expand-Archive -Path "$TestDrive$($DS)NonExistingArchive" -DestinationPath "$TestDrive$($DS)SourceDir"; throw "Expand-Archive did NOT throw expected error" } - catch { $_.FullyQualifiedErrorId | Should Be "ArchiveCmdletPathNotFound,Expand-Archive" } - - try { Expand-Archive -LiteralPath "$TestDrive$($DS)NonExistingArchive" -DestinationPath "$TestDrive$($DS)SourceDir"; throw "Expand-Archive did NOT throw expected error" } - catch { $_.FullyQualifiedErrorId | Should Be "ArchiveCmdletPathNotFound,Expand-Archive" } - } - - It "Validate error from Expand-Archive when invalid path (non-existing path / non-filesystem path) is supplied for DestinationPath parameter" { - $sourcePath = "$TestDrive$($DS)SamplePreCreatedArchive.zip" - $destinationPath = "HKLM:\SOFTWARE" - if ($IsWindows) { - $expectedError = "InvalidDirectoryPath,Expand-Archive" - } - else { - $expectedError = "DriveNotFound,Microsoft.PowerShell.Commands.NewItemCommand" - } - try { Expand-Archive -Path $sourcePath -DestinationPath $destinationPath; throw "Expand-Archive did NOT throw expected error" } - catch { $_.FullyQualifiedErrorId | Should Be $expectedError } - } - - It "Validate that you can compress an archive to a custom PSDrive using the Compress-Archive cmdlet" { - $sourcePath = "$TestDrive$($DS)SourceDir$($DS)ChildDir-1$($DS)Sample-3.txt" - $destinationDriveName = 'CompressArchivePesterTest' - $destinationDrive = New-PSDrive -Name $destinationDriveName -PSProvider FileSystem -Root $TestDrive -Scope Global - $destinationPath = "${destinationDriveName}:$($DS)CompressToPSDrive.zip" - try { - Compress-Archive -Path $sourcePath -DestinationPath $destinationPath - $destinationPath | Should Exist - } finally { - Remove-PSDrive -LiteralName $destinationDriveName - } - } - } - - Context "Expand-Archive - functional test cases" { - - It "Validate basic Expand-Archive scenario" { - $sourcePath = "$TestDrive$($DS)SamplePreCreatedArchive.zip" - $content = "Some Data" - $destinationPath = "$TestDrive$($DS)DestDirForBasicExpand" - $files = @("Sample-1.txt", "Sample-2.txt") - - # The files in "$TestDrive$($DS)SamplePreCreatedArchive.zip" are precreated. - $fileCreationTimeStamp = Get-Date -Year 2014 -Month 6 -Day 13 -Hour 15 -Minute 50 -Second 20 -Millisecond 0 - - Expand-Archive -Path $sourcePath -DestinationPath $destinationPath - foreach($currentFile in $files) - { - $expandedFile = Join-Path $destinationPath -ChildPath $currentFile - Test-Path $expandedFile | Should Be $True - - # We are validating to make sure that time stamps are preserved in the - # compressed archive are reflected back when the file is expanded. - (dir $expandedFile).LastWriteTime.CompareTo($fileCreationTimeStamp) | Should Be 0 - - Get-Content $expandedFile | Should Be $content - } - } - It "Validate that Expand-Archive cmdlet errors out when DestinationPath resolves to multiple locations" { - New-Item $TestDrive$($DS)SampleDir$($DS)Child-1 -Type Directory -Force | Out-Null - New-Item $TestDrive$($DS)SampleDir$($DS)Child-2 -Type Directory -Force | Out-Null - - $destinationPath = "$TestDrive$($DS)SampleDir$($DS)Child-*" - $sourcePath = "$TestDrive$($DS)SamplePreCreatedArchive.zip" - try - { - Expand-Archive -Path $sourcePath -DestinationPath $destinationPath - throw "Failed to detect that destination $destinationPath can resolve to multiple paths" - } - catch - { - $_.FullyQualifiedErrorId | Should Be "InvalidDestinationPath,Expand-Archive" - } - finally - { - Remove-Item -LiteralPath $TestDrive$($DS)SampleDir -Force -Recurse - } - } - It "Validate that Expand-Archive cmdlet works when DestinationPath resolves has wild card pattern and resolves to a single valid path" { - New-Item $TestDrive$($DS)SampleDir$($DS)Child-1 -Type Directory -Force | Out-Null - - $destinationPath = "$TestDrive$($DS)SampleDir$($DS)Child-*" - $sourcePath = "$TestDrive$($DS)SamplePreCreatedArchive.zip" - try - { - Expand-Archive -Path $sourcePath -DestinationPath $destinationPath - $expandedFiles = Get-ChildItem $destinationPath -Recurse - $expandedFiles.Length | Should BeGreaterThan 1 - } - finally - { - Remove-Item -LiteralPath $TestDrive$($DS)SampleDir -Force -Recurse - } - } - It "Validate Expand-Archive scenario where DestinationPath has Special Characters" { - $sourcePath = "$TestDrive$($DS)SamplePreCreatedArchive.zip" - $content = "Some Data" - $destinationPath = "$TestDrive$($DS)DestDir[]Expand" - $files = @("Sample-1.txt", "Sample-2.txt") - - # The files in "$TestDrive$($DS)SamplePreCreatedArchive.zip" are precreated. - $fileCreationTimeStamp = Get-Date -Year 2014 -Month 6 -Day 13 -Hour 15 -Minute 50 -Second 20 -Millisecond 0 - - Expand-Archive -Path $sourcePath -DestinationPath $destinationPath - foreach($currentFile in $files) - { - $expandedFile = Join-Path $destinationPath -ChildPath $currentFile - Test-Path -LiteralPath $expandedFile | Should Be $True - - # We are validating to make sure that time stamps are preserved in the - # compressed archive are reflected back when the file is expanded. - (dir -LiteralPath $expandedFile).LastWriteTime.CompareTo($fileCreationTimeStamp) | Should Be 0 - - Get-Content -LiteralPath $expandedFile | Should Be $content - } - } - It "Invoke Expand-Archive with relative path in Path parameter and -Force parameter" { - $sourcePath = ".$($DS)SamplePreCreatedArchive.zip" - $destinationPath = "$TestDrive$($DS)SomeOtherNonExistingDir$($DS)Path" - try - { - Push-Location $TestDrive - - Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -Force - $expandedFiles = Get-ChildItem $destinationPath -Recurse - $expandedFiles.Length | Should Be 2 - } - finally - { - Pop-Location - } - } - - It "Invoke Expand-Archive with relative path in LiteralPath parameter and -Force parameter" { - $sourcePath = ".$($DS)SamplePreCreatedArchive.zip" - $destinationPath = "$TestDrive$($DS)SomeOtherNonExistingDir$($DS)LiteralPath" - try - { - Push-Location $TestDrive - - Expand-Archive -LiteralPath $sourcePath -DestinationPath $destinationPath -Force - $expandedFiles = Get-ChildItem $destinationPath -Recurse - $expandedFiles.Length | Should Be 2 - } - finally - { - Pop-Location - } - } - - It "Invoke Expand-Archive with non-existing relative directory in DestinationPath parameter and -Force parameter" { - $sourcePath = "$TestDrive$($DS)SamplePreCreatedArchive.zip" - $destinationPath = ".$($DS)SomeOtherNonExistingDir$($DS)DestinationPath" - try - { - Push-Location $TestDrive - - Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -Force - $expandedFiles = Get-ChildItem $destinationPath -Recurse - $expandedFiles.Length | Should Be 2 - } - finally - { - Pop-Location - } - } - - # The test below is no longer valid. You can have zip files with non-zip extensions. Different archive - # formats should be added in a separate pull request, with a parameter to identify the archive format, and - # default formats associated with specific extensions. Until then, as long as these cmdlets only support - # Zip files, any file extension is supported. - #It "Invoke Expand-Archive with unsupported archive format" { - # $sourcePath = "$TestDrive$($DS)Sample.cab" - # $destinationPath = "$TestDrive$($DS)UnsupportedArchiveFormatDir" - # try - # { - # Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -Force - # throw "Failed to detect unsupported archive format at $sourcePath" - # } - # catch - # { - # $_.FullyQualifiedErrorId | Should Be "NotSupportedArchiveFileExtension,Expand-Archive" - # } - #} - - It "Invoke Expand-Archive with archive file containing multiple files, directories with subdirectories and empty directories" { - $sourcePath = "$TestDrive$($DS)SourceDir" - $archivePath = "$TestDrive$($DS)FileAndDirTreeForExpand.zip" - $destinationPath = "$TestDrive$($DS)FileAndDirTree" - $sourceList = dir $sourcePath -Name - - Add-CompressionAssemblies - [System.IO.Compression.ZipFile]::CreateFromDirectory($sourcePath, $archivePath) - - Expand-Archive -Path $archivePath -DestinationPath $destinationPath - $extractedList = dir $destinationPath -Name - - Compare-Object -ReferenceObject $extractedList -DifferenceObject $sourceList -PassThru | Should Be $null - } - - It "Validate Expand-Archive cmdlet in pipleline scenario" { - $sourcePath = "$TestDrive$($DS)SamplePreCreated*.zip" - $destinationPath = "$TestDrive$($DS)PipeToExpandArchive" - - $content = "Some Data" - $files = @("Sample-1.txt", "Sample-2.txt") - - dir $sourcePath | Expand-Archive -DestinationPath $destinationPath - - foreach($currentFile in $files) - { - $expandedFile = Join-Path $destinationPath -ChildPath $currentFile - Test-Path $expandedFile | Should Be $True - Get-Content $expandedFile | Should Be $content - } - } - - It "Validate that Expand-Archive generates Verbose messages" { - $sourcePath = "$TestDrive$($DS)SamplePreCreatedArchive.zip" - $destinationPath = "$TestDrive$($DS)VerboseMessagesInExpandArchive" - - try - { - $ps=[PowerShell]::Create() - $ps.Streams.Error.Clear() - $ps.Streams.Verbose.Clear() - $script = "Import-Module Microsoft.PowerShell.Archive; Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -Verbose" - $ps.AddScript($script) - $ps.Invoke() - - $ps.Streams.Verbose.Count -gt 0 | Should Be $True - $ps.Streams.Error.Count | Should Be 0 - } - finally - { - $ps.Dispose() - } - } - - It "Validate that without -Force parameter Expand-Archive generates non-terminating errors without overwriting existing files" { - $sourcePath = "$TestDrive$($DS)SamplePreCreatedArchive.zip" - $destinationPath = "$TestDrive$($DS)NoForceParameterExpandArchive" - - try - { - $ps=[PowerShell]::Create() - $ps.Streams.Error.Clear() - $ps.Streams.Verbose.Clear() - $script = "Import-Module Microsoft.PowerShell.Archive; Expand-Archive -Path $sourcePath -DestinationPath $destinationPath; Expand-Archive -Path $sourcePath -DestinationPath $destinationPath" - $ps.AddScript($script) - $ps.Invoke() - - $ps.Streams.Error.Count -gt 0 | Should Be $True - } - finally - { - $ps.Dispose() - } - } - - It "Validate that without DestinationPath parameter Expand-Archive cmdlet succeeds in expanding the archive" { - $sourcePath = "$TestDrive$($DS)SamplePreCreatedArchive.zip" - $archivePath = "$TestDrive$($DS)NoDestinationPathParameter.zip" - $destinationPath = "$TestDrive$($DS)NoDestinationPathParameter" - copy $sourcePath $archivePath -Force - - try - { - Push-Location $TestDrive - - Expand-Archive -Path $archivePath - (dir $destinationPath).Count | Should Be 2 - } - finally - { - Pop-Location - } - } - - It "Validate that without DestinationPath parameter Expand-Archive cmdlet succeeds in expanding the archive when destination directory exists" { - $sourcePath = "$TestDrive$($DS)SamplePreCreatedArchive.zip" - $archivePath = "$TestDrive$($DS)NoDestinationPathParameterDirExists.zip" - $destinationPath = "$TestDrive$($DS)NoDestinationPathParameterDirExists" - copy $sourcePath $archivePath -Force - New-Item -Path $destinationPath -ItemType Directory | Out-Null - - try - { - Push-Location $TestDrive - - Expand-Archive -Path $archivePath - (dir $destinationPath).Count | Should Be 2 - } - finally - { - Pop-Location - } - } - - It "Validate that Expand-Archive returns nothing when -PassThru is not used" { - $sourcePath = "$TestDrive$($DS)SourceDir" - $archivePath = "$TestDrive$($DS)NoPassThruTestForExpand.zip" - $destinationPath = "$TestDrive$($DS)NoPassThruTest" - - $sourceList = dir $sourcePath -Name - - Add-CompressionAssemblies - [System.IO.Compression.ZipFile]::CreateFromDirectory($sourcePath, $archivePath) - - $contents = Expand-Archive -Path $archivePath -DestinationPath $destinationPath - - $contents | Should Be $null - } - - It "Validate that Expand-Archive returns nothing when -PassThru is used with a value of $false" { - $sourcePath = "$TestDrive$($DS)SourceDir" - $archivePath = "$TestDrive$($DS)FalsePassThruTestForExpand.zip" - $destinationPath = "$TestDrive$($DS)FalsePassThruTest" - $sourceList = dir $sourcePath -Name - - Add-CompressionAssemblies - [System.IO.Compression.ZipFile]::CreateFromDirectory($sourcePath, $archivePath) - - $contents = Expand-Archive -Path $archivePath -DestinationPath $destinationPath -PassThru:$false - - $contents | Should Be $null - } - - It "Validate that Expand-Archive returns the contents of the archive -PassThru" { - $sourcePath = "$TestDrive$($DS)SourceDir" - $archivePath = "$TestDrive$($DS)PassThruTestForExpand.zip" - $destinationPath = "$TestDrive$($DS)PassThruTest" - $sourceList = dir $sourcePath -Name - - Add-CompressionAssemblies - [System.IO.Compression.ZipFile]::CreateFromDirectory($sourcePath, $archivePath) - - $contents = Expand-Archive -Path $archivePath -DestinationPath $destinationPath -PassThru | Sort-Object -Property PSParentPath,PSIsDirectory,Name - # We pipe Get-ChildItem to Get-Item here because the ToString results are different between the two, and we - # need to compare with other Get-Item results - $extractedList = Get-ChildItem -Recurse -LiteralPath $destinationPath | Get-Item - - Compare-Object -ReferenceObject $extractedList -DifferenceObject $contents -PassThru | Should Be $null - } - - It "Validate Expand-Archive works with zip files that have non-zip file extensions" { - $sourcePath = "$TestDrive$($DS)SourceDir" - $archivePath = "$TestDrive$($DS)NonZipFileExtension.dat" - $destinationPath = "$TestDrive$($DS)NonZipFileExtension" - $sourceList = dir $sourcePath -Name - - Add-CompressionAssemblies - [System.IO.Compression.ZipFile]::CreateFromDirectory($sourcePath, $archivePath) - - Expand-Archive -Path $archivePath -DestinationPath $destinationPath - $extractedList = dir $destinationPath -Name - - Compare-Object -ReferenceObject $extractedList -DifferenceObject $sourceList -PassThru | Should Be $null - } - - # trailing spaces give this error on Linux: Exception calling "[System.IO.Compression.ZipFileExtensions]::ExtractToFile" with "3" argument(s): "Could not find a part of the path '/tmp/02132f1d-5b0c-4a99-b5bf-707cef7681a6/TrailingSpacer/Inner/TrailingSpace/test.txt'." - It "Validate Expand-Archive works with zip files where the contents contain trailing whitespace" -skip:(!$IsWindows) { - $archivePath = "$TestDrive$($DS)TrailingSpacer.zip" - $destinationPath = "$TestDrive$($DS)TrailingSpacer" - # we can't just compare the output and the results as you only get one DirectoryInfo for directories that only contain directories - $expectedPaths = "$TestDrive$($DS)TrailingSpacer$($DS)Inner$($DS)TrailingSpace","$TestDrive$($DS)TrailingSpacer$($DS)Inner$($DS)TrailingSpace$($DS)test.txt" - - $contents = Expand-Archive -Path $archivePath -DestinationPath $destinationPath -PassThru - - $contents.Count | Should Be $expectedPaths.Count - - for ($i = 0; $i -lt $expectedPaths.Count; $i++) { - $contents[$i].FullName | Should Be $expectedPaths[$i] - } - } - - It "Validate that Compress-Archive/Expand-Archive work with backslashes and forward slashes in paths" { - $sourcePath = "$TestDrive\SourceDir/ChildDir-2" - $archivePath = "$TestDrive\MixedSlashesDir1/MixedSlashesDir2/SampleMixedslashFile.zip" - $expandPath = "$TestDrive\MixedSlashesExpandDir/DirA\DirB/DirC" - - New-Item -Path (Split-Path $archivePath) -Type Directory | Out-Null - Compress-Archive -Path $sourcePath -DestinationPath $archivePath - $archivePath | Should Exist - - $content = "Some Data" - $files = @("ChildDir-2$($DS)Sample-5.txt", "ChildDir-2$($DS)Sample-6.txt") - Expand-Archive -Path $archivePath -DestinationPath $expandPath - foreach($currentFile in $files) - { - $expandedFile = Join-Path $expandPath -ChildPath $currentFile - Test-Path $expandedFile | Should Be $True - Get-Content $expandedFile | Should Be $content - } - } - - It "Validate that Compress-Archive/Expand-Archive work with dates earlier than 1980" { - $file1 = New-Item $TestDrive$($DS)SourceDir$($DS)EarlierThan1980.txt -Type File -Force - $file1.LastWriteTime = [DateTime]::Parse('1974-10-03T04:30:00') - $file2 = Get-Item "$TestDrive$($DS)SourceDir$($DS)Sample-1.txt" - $expandPath = "$TestDrive$($DS)EarlyYearDir" - $expectedFile1 = "$expandPath$($DS)EarlierThan1980.txt" - $expectedFile2 = "$expandPath$($DS)Sample-1.txt" - $archivePath = "$TestDrive$($DS)EarlyYear.zip" - - try - { - Compress-Archive -Path @($file1, $file2) -DestinationPath $archivePath -WarningAction SilentlyContinue - $archivePath | Should Exist - - Expand-Archive -Path $archivePath -DestinationPath $expandPath - - $expectedFile1 | Should Exist - (Get-Item $expectedFile1).LastWriteTime | Should Be $([DateTime]::Parse('1980-01-01T00:00')) - $expectedFile2 | Should Exist - (Get-Item $expectedFile2).LastWriteTime | Should Not Be $([DateTime]::Parse('1980-01-01T00:00')) - - } - finally - { - Remove-Item -LiteralPath $archivePath -Force -Recurse - } - } - - # test is currently blocked by https://github.com/dotnet/corefx/issues/24832 - It "Validate module can be imported when current language is not en-US" -Pending { - $currentCulture = [System.Threading.Thread]::CurrentThread.CurrentUICulture - try { - [System.Threading.Thread]::CurrentThread.CurrentCulture = [CultureInfo]::new("he-IL") - { Import-Module Microsoft.PowerShell.Archive -Force -ErrorAction Stop } | Should Not Throw - } - finally { - [System.Threading.Thread]::CurrentThread.CurrentCulture = $currentCulture - } - } - } -} \ No newline at end of file diff --git a/Tests/SamplePreCreatedArchive.archive b/Tests/SamplePreCreatedArchive.archive deleted file mode 100644 index c2aecb4..0000000 Binary files a/Tests/SamplePreCreatedArchive.archive and /dev/null differ diff --git a/Tests/TrailingSpacer.archive b/Tests/TrailingSpacer.archive deleted file mode 100644 index f8e2c3e..0000000 Binary files a/Tests/TrailingSpacer.archive and /dev/null differ diff --git a/TravisCI.ps1 b/TravisCI.ps1 deleted file mode 100644 index a605497..0000000 --- a/TravisCI.ps1 +++ /dev/null @@ -1,6 +0,0 @@ -$testResultsFile = "./ArchiveTestResults.xml" -Import-Module "./Microsoft.PowerShell.Archive/Microsoft.PowerShell.Archive.psd1" -Force -$testResults = Invoke-Pester -Script "./Tests" -OutputFormat NUnitXml -OutputFile $testResultsFile -PassThru -if ($testResults.FailedCount -gt 0) { - throw "$($testResults.FailedCount) tests failed." -} diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index ac70956..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,22 +0,0 @@ -# Image with WMF5.0 RTM -os: "WMF 5" - -# clone directory -clone_folder: c:\projects\Archive-Module - -# Install Pester -install: - - cinst -y pester - -build: false - -# Run Pester tests and store the results -test_script: - - ps: | - $testResultsFile = ".\ArchiveTestResults.xml" - Import-Module "C:\projects\Archive-Module\Microsoft.PowerShell.Archive" -Force - $testResults = Invoke-Pester -Script "C:\projects\Archive-Module\Tests" -OutputFormat NUnitXml -OutputFile $testResultsFile -PassThru - (New-Object 'System.Net.WebClient').UploadFile("https://ci.appveyor.com/api/testresults/nunit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path $testResultsFile)) - if ($testResults.FailedCount -gt 0) { - throw "$($testResults.FailedCount) tests failed." - } diff --git a/azure-pipelines-release.yml b/azure-pipelines-release.yml deleted file mode 100644 index c418b4b..0000000 --- a/azure-pipelines-release.yml +++ /dev/null @@ -1,102 +0,0 @@ -name: $(BuildDefinitionName)_$(date:yyMM).$(date:dd)$(rev:rrr) - -trigger: none - -resources: - repositories: - - repository: ComplianceRepo - type: github - endpoint: ComplianceGHRepo - name: PowerShell/compliance - -variables: - - name: PackageName - value: 'Microsoft.PowerShell.Archive' - - name: PackageVersion - value: '' - - name: BuildOutDir - value: '' - -stages: -- stage: Build - displayName: Build module - pool: - name: 1ES - demands: - - ImageOverride -equals PSMMS2019-Secure - jobs: - - job: BuildPkg - displayName: Build module - variables: - - group: ESRP - steps: - - - pwsh: | - & $(Build.SourcesDirectory)\SimpleBuild.ps1 - displayName: Build Microsoft.PowerShell.Archive module - condition: succeededOrFailed() - - - pwsh: | - dir "$(BuildOutDir)\*" -Recurse - displayName: Show BuildOutDirectory - - - template: Sbom.yml@ComplianceRepo - parameters: - BuildDropPath: "$(BuildOutDir)" - Build_Repository_Uri: 'https://github.com/PowerShell/Microsoft.PowerShell.Archive' - PackageName: $(PackageName) - PackageVersion: $(PackageVersion) - - - pwsh: | - dir "$(BuildOutDir)\*" -Recurse - displayName: Show BuildOutDirectory - - - pwsh: | - $signSrcPath = "$(BuildOutDir)" - # Set signing src path variable - $vstsCommandString = "vso[task.setvariable variable=signSrcPath]${signSrcPath}" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - $signOutPath = "$(Build.SourcesDirectory)\signed\Microsoft.PowerShell.Archive" - $null = New-Item -ItemType Directory -Path $signOutPath - # Set signing out path variable - $vstsCommandString = "vso[task.setvariable variable=signOutPath]${signOutPath}" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - # Set path variable for guardian codesign validation - $vstsCommandString = "vso[task.setvariable variable=GDN_CODESIGN_TARGETDIRECTORY]${signOutPath}" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - displayName: Setup variables for signing - - - template: EsrpSign.yml@ComplianceRepo - parameters: - # the folder which contains the binaries to sign - buildOutputPath: $(signSrcPath) - # the location to put the signed output - signOutputPath: $(signOutPath) - # the certificate ID to use - certificateId: "CP-230012" - # the file pattern to use, comma separated - pattern: '*.psd1,*.psm1' - - - pwsh: | - Compress-Archive -Path "$(signOutPath)\*" -DestinationPath "$(System.ArtifactsDirectory)\Microsoft.PowerShell.Archive.zip" - displayName: Create Microsoft.PowerShell.Archive.zip - - - publish: $(System.ArtifactsDirectory)\Microsoft.PowerShell.Archive.zip - artifact: SignedModule - - - template: script-module-compliance.yml@ComplianceRepo - parameters: - # component-governance - sourceScanPath: '$(signOutPath)' - # credscan - suppressionsFile: '' - # TermCheck - optionsRulesDBPath: '' - optionsFTPath: '' - # tsa-upload - codeBaseName: 'Microsoft_PowerShell_Archive_2_15_2022' - # selections - APIScan: false # set to false when not using Windows APIs. diff --git a/src/ArchiveAddition.cs b/src/ArchiveAddition.cs new file mode 100644 index 0000000..d0e8337 --- /dev/null +++ b/src/ArchiveAddition.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.PowerShell.Archive +{ + /// + /// ArchiveAddition represents an filesystem entry that we want to add to or update in the archive. + /// ArchiveAddition DOES NOT represent an entry in the archive -- rather, it represents an entry to be created or updated using the information contained in an instance of this class. + /// + internal class ArchiveAddition + { + /// + /// The name of the file or directory in the archive. + /// This is a path of the file or directory in the archive (e.g., 'file1.txt` means the file is a top-level file in the archive). + /// + /// Does EntryName == FileSystemInfo.Name? This is not always true because EntryName can contain ancestor directories due to path directory structure preservation or due to the user + /// archiving parent directories. + /// For example, supoose we have the following directory + /// grandparent + /// |---parent + /// |---file.txt + /// If we want to add or update grandparent to/in the archive, grandparent would be recursed for its descendents. This means the EntryName of file.txt would become + /// `grandparent/parent/file.txt` so that when expanding the archive, file.txt is put in the correct location (directly under parent and under grandparent). + /// + internal string EntryName { get; set; } + + internal System.IO.FileSystemInfo FileSystemInfo { get; set; } + + internal ArchiveAddition(string entryName, System.IO.FileSystemInfo fileSystemInfo) + { + EntryName = entryName; + FileSystemInfo = fileSystemInfo; + } + } +} diff --git a/src/ArchiveFactory.cs b/src/ArchiveFactory.cs new file mode 100644 index 0000000..fd0e636 --- /dev/null +++ b/src/ArchiveFactory.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.IO; + +namespace Microsoft.PowerShell.Archive +{ + internal static class ArchiveFactory + { + internal static IArchive GetArchive(ArchiveFormat format, string archivePath, ArchiveMode archiveMode, System.IO.Compression.CompressionLevel compressionLevel) + { + System.IO.FileStream archiveFileStream = archiveMode switch + { + ArchiveMode.Create => new FileStream(archivePath, mode: System.IO.FileMode.CreateNew, access: System.IO.FileAccess.Write, share: System.IO.FileShare.None), + ArchiveMode.Update => new FileStream(archivePath, mode: System.IO.FileMode.Open, access: System.IO.FileAccess.ReadWrite, share: System.IO.FileShare.None), + ArchiveMode.Extract => new FileStream(archivePath, mode: System.IO.FileMode.Open, access: System.IO.FileAccess.Read, share: System.IO.FileShare.Read), + _ => throw new ArgumentOutOfRangeException(nameof(archiveMode)) + }; + + return format switch + { + ArchiveFormat.Zip => new ZipArchive(archivePath, archiveMode, archiveFileStream, compressionLevel), + //ArchiveFormat.tar => new TarArchive(archivePath, archiveMode, archiveFileStream), + // TODO: Add Tar.gz here + _ => throw new ArgumentOutOfRangeException(nameof(archiveMode)) + }; + } + + internal static bool TryGetArchiveFormatFromExtension(string path, out ArchiveFormat? archiveFormat) + { + archiveFormat = Path.GetExtension(path).ToLowerInvariant() switch + { + ".zip" => ArchiveFormat.Zip, + /* Disable support for tar and tar.gz for preview1 release + ".gz" => path.EndsWith(".tar.gz) ? ArchiveFormat.Tgz : null, + */ + _ => null + }; + return archiveFormat is not null; + } + } +} diff --git a/src/ArchiveFormat.cs b/src/ArchiveFormat.cs new file mode 100644 index 0000000..5ee6fef --- /dev/null +++ b/src/ArchiveFormat.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.PowerShell.Archive +{ + public enum ArchiveFormat + { + Zip, + /* Removing these formats for preview relase + Tar, + Tgz*/ + } +} diff --git a/src/ArchiveMode.cs b/src/ArchiveMode.cs new file mode 100644 index 0000000..1f99a37 --- /dev/null +++ b/src/ArchiveMode.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.PowerShell.Archive +{ + internal enum ArchiveMode + { + Create, + Update, + Extract + } +} diff --git a/src/CompressArchiveCommand.cs b/src/CompressArchiveCommand.cs new file mode 100644 index 0000000..de521d9 --- /dev/null +++ b/src/CompressArchiveCommand.cs @@ -0,0 +1,386 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.IO.Compression; +using System.Management.Automation; +using System.Runtime.InteropServices; + +using Microsoft.PowerShell.Archive.Localized; + +namespace Microsoft.PowerShell.Archive +{ + [Cmdlet("Compress", "Archive", SupportsShouldProcess = true)] + [OutputType(typeof(FileInfo))] + public sealed class CompressArchiveCommand : PSCmdlet + { + + // TODO: Add filter parameter + // TODO: Add flatten parameter + // TODO: Add comments to methods + // TODO: Add tar support + + private enum ParameterSet + { + Path, + LiteralPath + } + + /// + /// The Path parameter - specifies paths of files or directories from the filesystem to add to or update in the archive. + /// This parameter does expand wildcard characters. + /// + [Parameter(Mandatory = true, Position = 0, ParameterSetName = nameof(ParameterSet.Path), ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] + [ValidateNotNullOrEmpty] + public string[]? Path { get; set; } + + /// + /// The LiteralPath parameter - specifies paths of files or directories from the filesystem to add to or update in the archive. + /// This parameter does not expand wildcard characters. + /// + [Parameter(Mandatory = true, ParameterSetName = nameof(ParameterSet.LiteralPath), ValueFromPipelineByPropertyName = true)] + [ValidateNotNullOrEmpty] + [Alias("PSPath", "LP")] + public string[]? LiteralPath { get; set; } + + /// + /// The DestinationPath parameter - specifies the location of the archive in the filesystem. + /// + [Parameter(Mandatory = true, Position = 1)] + [ValidateNotNullOrEmpty] + [NotNull] + public string? DestinationPath { get; set; } + + [Parameter()] + public WriteMode WriteMode { get; set; } + + [Parameter()] + public SwitchParameter PassThru { get; set; } + + [Parameter()] + [ValidateNotNullOrEmpty] + public CompressionLevel CompressionLevel { get; set; } + + [Parameter()] + public ArchiveFormat? Format { get; set; } = null; + + private readonly PathHelper _pathHelper; + + private bool _didCreateNewArchive; + + // Stores paths + private HashSet? _paths; + + // This is used so the cmdlet can show all nonexistent paths at once to the user + private HashSet _nonexistentPaths; + + // Keeps track of duplicate paths so the cmdlet can show them all at once to the user + private HashSet _duplicatePaths; + + // Keeps track of whether any source path is equal to the destination path + // Since we are already checking for duplicates, only a bool is necessary and not a List or a HashSet + // Only 1 path could be equal to the destination path after filtering for duplicates + private bool _isSourcePathEqualToDestinationPath; + + public CompressArchiveCommand() + { + _pathHelper = new PathHelper(this); + Messages.Culture = new System.Globalization.CultureInfo("en-US"); + _didCreateNewArchive = false; + _paths = new HashSet( RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase); + _nonexistentPaths = new HashSet( RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase); + _duplicatePaths = new HashSet( RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase); + } + + protected override void BeginProcessing() + { + // This resolves the path to a fully qualified path and handles provider exceptions + DestinationPath = _pathHelper.GetUnresolvedPathFromPSProviderPath(DestinationPath); + ValidateDestinationPath(); + } + + protected override void ProcessRecord() + { + if (ParameterSetName == nameof(ParameterSet.Path)) + { + Debug.Assert(Path is not null); + foreach (var path in Path) { + var resolvedPaths = _pathHelper.GetResolvedPathFromPSProviderPath(path, _nonexistentPaths); + if (resolvedPaths is not null) { + foreach (var resolvedPath in resolvedPaths) { + // Add resolvedPath to _path + AddPathToPaths(pathToAdd: resolvedPath); + } + } + } + + } + else + { + Debug.Assert(LiteralPath is not null); + foreach (var path in LiteralPath) { + var unresolvedPath = _pathHelper.GetUnresolvedPathFromPSProviderPath(path, _nonexistentPaths); + if (unresolvedPath is not null) { + // Add unresolvedPath to _path + AddPathToPaths(pathToAdd: unresolvedPath); + } + } + } + } + + protected override void EndProcessing() + { + // If there are non-existent paths, throw a terminating error + if (_nonexistentPaths.Count > 0) { + // Get a comma-seperated string containg the non-existent paths + string commaSeperatedNonExistentPaths = string.Join(',', _nonexistentPaths); + var errorRecord = ErrorMessages.GetErrorRecord(ErrorCode.PathNotFound, commaSeperatedNonExistentPaths); + ThrowTerminatingError(errorRecord); + } + + // If there are duplicate paths, throw a terminating error + if (_duplicatePaths.Count > 0) { + // Get a comma-seperated string containg the non-existent paths + string commaSeperatedDuplicatePaths = string.Join(',', _nonexistentPaths); + var errorRecord = ErrorMessages.GetErrorRecord(ErrorCode.DuplicatePaths, commaSeperatedDuplicatePaths); + ThrowTerminatingError(errorRecord); + } + + // If a source path is the same as the destination path, throw a terminating error + // We don't want to overwrite the file or directory that we want to add to the archive. + if (_isSourcePathEqualToDestinationPath) { + var errorCode = ParameterSetName == nameof(ParameterSet.Path) ? ErrorCode.SamePathAndDestinationPath : ErrorCode.SameLiteralPathAndDestinationPath; + var errorRecord = ErrorMessages.GetErrorRecord(errorCode); + ThrowTerminatingError(errorRecord); + } + + // Get archive entries + // If a path causes an exception (e.g., SecurityException), _pathHelper should handle it + List archiveAdditions = _pathHelper.GetArchiveAdditions(_paths); + + // Remove references to _paths, Path, and LiteralPath to free up memory + // The user could have supplied a lot of paths, so we should do this + Path = null; + LiteralPath = null; + _paths = null; + + // Warn the user if there are no items to add for some reason (e.g., no items matched the filter) + if (archiveAdditions.Count == 0) + { + WriteWarning(Messages.NoItemsToAddWarning); + } + + // Get the ArchiveMode for the archive to be created or updated + ArchiveMode archiveMode = WriteMode == WriteMode.Update ? ArchiveMode.Update : ArchiveMode.Create; + + // Don't create the archive object yet because the user could have specified -WhatIf or -Confirm + IArchive? archive = null; + try + { + if (ShouldProcess(target: DestinationPath, action: Messages.Create)) + { + // If the WriteMode is overwrite, delete the existing archive + if (WriteMode == WriteMode.Overwrite) + { + DeleteDestinationPathIfExists(); + } + + // Create an archive -- this is where we will switch between different types of archives + archive = ArchiveFactory.GetArchive(format: Format ?? ArchiveFormat.Zip, archivePath: DestinationPath, archiveMode: archiveMode, compressionLevel: CompressionLevel); + _didCreateNewArchive = archiveMode != ArchiveMode.Update; + } + + long numberOfAdditions = archiveAdditions.Count; + long numberOfAddedItems = 0; + // Messages.ProgressDisplay does not need to be formatted here because progressRecord.StautsDescription will be updated in the for-loop + var progressRecord = new ProgressRecord(activityId: 1, activity: "Compress-Archive", statusDescription: Messages.ProgressDisplay); + + foreach (ArchiveAddition entry in archiveAdditions) + { + // Update progress + var percentComplete = numberOfAddedItems / (float)numberOfAdditions * 100f; + progressRecord.StatusDescription = string.Format(Messages.ProgressDisplay, "{percentComplete:0.0}"); + WriteProgress(progressRecord); + + if (ShouldProcess(target: entry.FileSystemInfo.FullName, action: Messages.Add)) + { + archive?.AddFileSystemEntry(entry); + // Write a verbose message saying this item was added to the archive + var addedItemMessage = string.Format(Messages.AddedItemToArchiveVerboseMessage, entry.FileSystemInfo.FullName); + WriteVerbose(addedItemMessage); + } + // Keep track of number of items added to the archive + numberOfAddedItems++; + } + + // Once all items in the archive are processed, show progress as 100% + // This code is here and not in the loop because we want it to run even if there are no items to add to the archive + progressRecord.StatusDescription = string.Format(Messages.ProgressDisplay, "100.0"); + WriteProgress(progressRecord); + } + finally + { + archive?.Dispose(); + } + + // If -PassThru is specified, write a System.IO.FileInfo object + if (PassThru) + { + WriteObject(new FileInfo(DestinationPath)); + } + } + + protected override void StopProcessing() + { + // If a new output archive was created, delete it (this does not delete an archive if -WriteMode Update is specified) + if (_didCreateNewArchive) + { + DeleteDestinationPathIfExists(); + } + } + + /// + /// Validate DestinationPath parameter and determine the archive format based on the extension of DestinationPath + /// + private void ValidateDestinationPath() + { + ErrorCode? errorCode = null; + + if (System.IO.Path.Exists(DestinationPath)) + { + // Check if DestinationPath is an existing directory + if (Directory.Exists(DestinationPath)) + { + // Throw an error if DestinationPath exists and the cmdlet is not in Update mode or Overwrite is not specified + if (WriteMode == WriteMode.Create) + { + errorCode = ErrorCode.ArchiveExistsAsDirectory; + } + // Throw an error if the DestinationPath is a directory and the cmdlet is in Update mode + else if (WriteMode == WriteMode.Update) + { + errorCode = ErrorCode.ArchiveExistsAsDirectory; + } + // Throw an error if the DestinationPath is the current working directory and the cmdlet is in Overwrite mode + else if (WriteMode == WriteMode.Overwrite && DestinationPath == SessionState.Path.CurrentFileSystemLocation.ProviderPath) + { + errorCode = ErrorCode.CannotOverwriteWorkingDirectory; + } + // Throw an error if the DestinationPath is a directory with at 1 least item and the cmdlet is in Overwrite mode + else if (WriteMode == WriteMode.Overwrite && Directory.GetFileSystemEntries(DestinationPath).Length > 0) + { + errorCode = ErrorCode.ArchiveIsNonEmptyDirectory; + } + } + // If DestinationPath is an existing file + else + { + // Throw an error if DestinationPath exists and the cmdlet is not in Update mode or Overwrite is not specified + if (WriteMode == WriteMode.Create) + { + errorCode = ErrorCode.ArchiveExists; + } + // Throw an error if the cmdlet is in Update mode but the archive is read only + else if (WriteMode == WriteMode.Update && File.GetAttributes(DestinationPath).HasFlag(FileAttributes.ReadOnly)) + { + errorCode = ErrorCode.ArchiveReadOnly; + } + } + } + // Throw an error if DestinationPath does not exist and cmdlet is in Update mode + else if (WriteMode == WriteMode.Update) + { + errorCode = ErrorCode.ArchiveDoesNotExist; + } + + if (errorCode is not null) + { + // Throw an error -- since we are validating DestinationPath, the problem is with DestinationPath + var errorRecord = ErrorMessages.GetErrorRecord(errorCode: errorCode.Value, errorItem: DestinationPath); + ThrowTerminatingError(errorRecord); + } + + // Determine archive format based on the extension of DestinationPath + DetermineArchiveFormat(); + } + + private void DeleteDestinationPathIfExists() + { + try + { + // No need to ensure DestinationPath has no children when deleting it + // because ValidateDestinationPath should have already done this + if (File.Exists(DestinationPath)) + { + File.Delete(DestinationPath); + } + else if (Directory.Exists(DestinationPath)) + { + Directory.Delete(DestinationPath); + } + } + // Throw a terminating error if an IOException occurs + catch (IOException ioException) + { + var errorRecord = new ErrorRecord(ioException, errorId: nameof(ErrorCode.OverwriteDestinationPathFailed), + errorCategory: ErrorCategory.InvalidOperation, targetObject: DestinationPath); + ThrowTerminatingError(errorRecord); + } + // Throw a terminating error if an UnauthorizedAccessException occurs + catch (System.UnauthorizedAccessException unauthorizedAccessException) + { + var errorRecord = new ErrorRecord(unauthorizedAccessException, errorId: nameof(ErrorCode.InsufficientPermissionsToAccessPath), + errorCategory: ErrorCategory.PermissionDenied, targetObject: DestinationPath); + ThrowTerminatingError(errorRecord); + } + } + + private void DetermineArchiveFormat() + { + // Check if cmdlet is able to determine the format of the archive based on the extension of DestinationPath + bool ableToDetermineArchiveFormat = ArchiveFactory.TryGetArchiveFormatFromExtension(path: DestinationPath, archiveFormat: out var archiveFormat); + // If the user did not specify which archive format to use, try to determine it automatically + if (Format is null) + { + if (ableToDetermineArchiveFormat) + { + Format = archiveFormat; + } + else + { + // If the archive format could not be determined, use zip by default and emit a warning + var warningMsg = string.Format(Messages.ArchiveFormatCouldNotBeDeterminedWarning, DestinationPath); + WriteWarning(warningMsg); + Format = ArchiveFormat.Zip; + } + // Write a verbose message saying that Format is not specified and a format was determined automatically + string verboseMessage = string.Format(Messages.ArchiveFormatDeterminedVerboseMessage, Format); + WriteVerbose(verboseMessage); + } + // If the user did specify which archive format to use, emit a warning if DestinationPath does not match the chosen archive format + else + { + if (archiveFormat is null || archiveFormat.Value != Format.Value) + { + var warningMsg = string.Format(Messages.ArchiveExtensionDoesNotMatchArchiveFormatWarning, DestinationPath); + WriteWarning(warningMsg); + } + } + } + + // Adds a path to _paths variable + // If the path being added is a duplicate, it adds it _duplicatePaths (if it is not already there) + // If the path is the same as the destination path, it sets _isSourcePathEqualToDestinationPath to true + private void AddPathToPaths(string pathToAdd) { + if (!_paths.Add(pathToAdd)) { + _duplicatePaths.Add(pathToAdd); + } else if (!_isSourcePathEqualToDestinationPath && pathToAdd == DestinationPath) { + _isSourcePathEqualToDestinationPath = true; + } + } + } +} diff --git a/src/ErrorMessages.cs b/src/ErrorMessages.cs new file mode 100644 index 0000000..80ec0c5 --- /dev/null +++ b/src/ErrorMessages.cs @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.PowerShell.Archive.Localized; +using System; +using System.Management.Automation; + +namespace Microsoft.PowerShell.Archive +{ + internal static class ErrorMessages + { + internal static ErrorRecord GetErrorRecord(ErrorCode errorCode, string errorItem) + { + var errorMsg = string.Format(GetErrorMessage(errorCode: errorCode), errorItem); + var exception = new ArgumentException(errorMsg); + return new ErrorRecord(exception, errorCode.ToString(), ErrorCategory.InvalidArgument, errorItem); + } + + internal static ErrorRecord GetErrorRecord(ErrorCode errorCode) + { + var errorMsg = GetErrorMessage(errorCode: errorCode); + var exception = new ArgumentException(errorMsg); + return new ErrorRecord(exception, errorCode.ToString(), ErrorCategory.InvalidArgument, null); + } + + internal static string GetErrorMessage(ErrorCode errorCode) + { + return errorCode switch + { + ErrorCode.PathNotFound => Messages.PathNotFoundMessage, + ErrorCode.InvalidPath => Messages.InvalidPathMessage, + ErrorCode.DuplicatePaths => Messages.DuplicatePathsMessage, + ErrorCode.ArchiveExists => Messages.ArchiveExistsMessage, + ErrorCode.ArchiveExistsAsDirectory => Messages.ArchiveExistsAsDirectoryMessage, + ErrorCode.ArchiveReadOnly => Messages.ArchiveIsReadOnlyMessage, + ErrorCode.ArchiveDoesNotExist => Messages.ArchiveDoesNotExistMessage, + ErrorCode.ArchiveIsNonEmptyDirectory => Messages.ArchiveIsNonEmptyDirectory, + ErrorCode.SamePathAndDestinationPath => Messages.SamePathAndDestinationPathMessage, + ErrorCode.SameLiteralPathAndDestinationPath => Messages.SameLiteralPathAndDestinationPathMessage, + ErrorCode.InsufficientPermissionsToAccessPath => Messages.InsufficientPermssionsToAccessPathMessage, + ErrorCode.OverwriteDestinationPathFailed => Messages.OverwriteDestinationPathFailed, + ErrorCode.CannotOverwriteWorkingDirectory => Messages.CannotOverwriteWorkingDirectoryMessage, + _ => throw new ArgumentOutOfRangeException(nameof(errorCode)) + }; + } + } + + internal enum ErrorCode + { + // Used when a path does not resolve to a file or directory on the filesystem + PathNotFound, + // Used when a path is invalid (e.g., if the path is for a non-filesystem provider) + InvalidPath, + // Used when when a path has been supplied to the cmdlet at least twice + DuplicatePaths, + // Used when DestinationPath is an existing file + ArchiveExists, + // Used when DestinationPath is an existing directory + ArchiveExistsAsDirectory, + // Used when DestinationPath is a non-empty directory and Action Overwrite is specified + ArchiveIsNonEmptyDirectory, + // Used when Compress-Archive cmdlet is in Update mode but the archive is read-only + ArchiveReadOnly, + // Used when DestinationPath does not exist and the Compress-Archive cmdlet is in Update mode + ArchiveDoesNotExist, + // Used when Path and DestinationPath are the same + SamePathAndDestinationPath, + // Used when LiteralPath and DestinationPath are the same + SameLiteralPathAndDestinationPath, + // Used when the user does not have sufficient permissions to access a path + InsufficientPermissionsToAccessPath, + // Used when the cmdlet could not overwrite DestinationPath + OverwriteDestinationPathFailed, + // Used when the user enters the working directory as DestinationPath and it is an existing folder and -WriteMode Overwrite is specified + CannotOverwriteWorkingDirectory + } +} diff --git a/src/IArchive.cs b/src/IArchive.cs new file mode 100644 index 0000000..a785a5f --- /dev/null +++ b/src/IArchive.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.PowerShell.Archive +{ + internal interface IArchive: IDisposable + { + // Get what mode the archive is in + internal ArchiveMode Mode { get; } + + // Get the fully qualified path of the archive + internal string Path { get; } + + // Add a file or folder to the archive. The entry name of the added item in the + // will be ArchiveEntry.Name. + // Throws an exception if the archive is in read mode. + internal void AddFileSystemEntry(ArchiveAddition entry); + + // Get the entries in the archive. + // Throws an exception if the archive is in create mode. + internal string[] GetEntries(); + + // Expands an archive to a destination folder. + // Throws an exception if the archive is not in read mode. + internal void Expand(string destinationPath); + } +} diff --git a/src/Localized/Messages.resx b/src/Localized/Messages.resx new file mode 100644 index 0000000..e230be9 --- /dev/null +++ b/src/Localized/Messages.resx @@ -0,0 +1,183 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + {0} was added to the archive. + + + The archive {0} does not exist. + + + The destination path {0} is a directory. + + + The destination path {0} already exists. + + + The archive {0} does not have an extension or an extension that matches the chosen archive format. + + + The format of the archive {0} could not determined by its extension. The zip format is chosen by default. + + + The -Format was not specified, so the archive format was determined to be {0} based on its extension. + + + The archive {0} cannot be overwritten because it is a non-empty directory. + + + The archive at {0} is read-only. + + + Unable to overwrite the path {0} because it is the same as the current working directory. + + + Create + + + The path(s) {0} have been specified more than once. + + + There are insufficient permissions to access the path {0}. + + + The path(s) {0} are invalid. + + + There are no items to add to the archive. + + + Could not overwrite the destination path. + + + The path {0} could not be found. + + + {0}% complete + + + A path supplied to -LiteralPath is the same as the path supplied to -DestinationPath. + + + A path supplied to -Path is the same as the path supplied to -DestinationPath. + + \ No newline at end of file diff --git a/src/Microsoft.PowerShell.Archive.csproj b/src/Microsoft.PowerShell.Archive.csproj new file mode 100644 index 0000000..b8bd133 --- /dev/null +++ b/src/Microsoft.PowerShell.Archive.csproj @@ -0,0 +1,46 @@ + + + + net7.0 + enable + en-US + Microsoft.PowerShell.Archive + true + false + + + + False + None + false + + + + + PreserveNewest + + + + + + + + + + True + True + Messages.resx + + + + + + ResXFileCodeGenerator + Messages.Designer.cs + CSharp + Microsoft.PowerShell.Archive.Localized + Messages + + + + diff --git a/src/Microsoft.PowerShell.Archive.psd1 b/src/Microsoft.PowerShell.Archive.psd1 new file mode 100644 index 0000000..a047d5a --- /dev/null +++ b/src/Microsoft.PowerShell.Archive.psd1 @@ -0,0 +1,22 @@ +@{ +ModuleVersion = '2.0.1' +GUID = '06a335eb-dd10-4d25-b753-4f6a80163516' +Author = 'Microsoft' +CompanyName = 'Microsoft' +Copyright = '(c) Microsoft. All rights reserved.' +Description = 'PowerShell module for creating and expanding archives.' +PowerShellVersion = '7.2.5' +NestedModules = @('Microsoft.PowerShell.Archive.dll') +CmdletsToExport = @('Compress-Archive') +PrivateData = @{ + PSData = @{ + Tags = @('Archive', 'Zip', 'Compress') + ProjectUri = 'https://github.com/PowerShell/Microsoft.PowerShell.Archive' + ReleaseNotes = @' + ## 2.0.1-preview1 + - Rewrote Compress-Archive cmdlet in C# +'@ + Prerelease = 'preview1' + } +} +} diff --git a/src/PathHelper.cs b/src/PathHelper.cs new file mode 100644 index 0000000..b5262ed --- /dev/null +++ b/src/PathHelper.cs @@ -0,0 +1,385 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.PowerShell.Archive.Localized; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Management.Automation; + +namespace Microsoft.PowerShell.Archive +{ + internal class PathHelper + { + private readonly PSCmdlet _cmdlet; + + private const string FileSystemProviderName = "FileSystem"; + + internal PathHelper(PSCmdlet cmdlet) + { + _cmdlet = cmdlet; + } + + internal List GetArchiveAdditions(HashSet fullyQualifiedPaths) + { + List archiveAdditions = new List(fullyQualifiedPaths.Count); + foreach (var path in fullyQualifiedPaths) + { + // Assume each path is valid, fully qualified, and existing + Debug.Assert(Path.Exists(path)); + Debug.Assert(Path.IsPathFullyQualified(path)); + AddAdditionForFullyQualifiedPath(path, archiveAdditions); + } + return archiveAdditions; + } + + /// + /// Adds an ArchiveAddition object for a path to a list of ArchiveAddition objects + /// + /// The fully qualified path + /// The list where to add the ArchiveAddition object for the path + /// If true, relative path structure will be preserved. If false, relative path structure will NOT be preserved. + private void AddAdditionForFullyQualifiedPath(string path, List additions) + { + Debug.Assert(Path.Exists(path)); + FileSystemInfo fileSystemInfo; + + if (Directory.Exists(path)) + { + // If the path is a directory, ensure it does not have a trailing DirectorySeperatorChar and if it does, remove it + // This will make path comparisons easier because the cmdlet won't have to consider whether or not a path has a DirectorySeperatorChar at the end + // i.e., we will avoid the scenario where 1 path has a trailing DirectorySeperatorChar and the other does not (this is not a big deal, but makes life easier) + if (path.EndsWith(Path.DirectorySeparatorChar)) { + path = path.Substring(0, path.Length - 1); + } + fileSystemInfo = new DirectoryInfo(path); + } + else + { + fileSystemInfo = new FileInfo(path); + } + + // Get the entry name of the file or directory in the archive + // The cmdlet will preserve the directory structure as long as the path is relative to the working directory + var entryName = GetEntryName(fileSystemInfo, out bool doesPreservePathStructure); + additions.Add(new ArchiveAddition(entryName: entryName, fileSystemInfo: fileSystemInfo)); + + // Recurse through the child items and add them to additions + if (fileSystemInfo.Attributes.HasFlag(FileAttributes.Directory) && fileSystemInfo is DirectoryInfo directoryInfo) { + AddDescendentEntries(directoryInfo: directoryInfo, additions: additions, shouldPreservePathStructure: doesPreservePathStructure); + } + } + + /// + /// Creates an ArchiveAdditon object for each child item of the directory and adds it to a list of ArchiveAddition objects + /// + /// A fully qualifed path referring to a directory + /// Where the ArchiveAddtion object for each child item of the directory will be added + /// See above + private void AddDescendentEntries(System.IO.DirectoryInfo directoryInfo, List additions, bool shouldPreservePathStructure) + { + try + { + // pathPrefix is used to construct the entry names of the descendents of the directory + var pathPrefix = GetPrefixForPath(directoryInfo: directoryInfo); + foreach (var childFileSystemInfo in directoryInfo.EnumerateFileSystemInfos("*", SearchOption.AllDirectories)) + { + string entryName; + // If the cmdlet should preserve the path structure, then use the relative path + if (shouldPreservePathStructure) + { + entryName = GetEntryName(childFileSystemInfo, out bool doesPreservePathStructure); + Debug.Assert(doesPreservePathStructure); + } + // Otherwise, get the entry name using the prefix + else + { + entryName = GetEntryNameUsingPrefix(path: childFileSystemInfo.FullName, prefix: pathPrefix); + } + // Add an entry for each descendent of the directory + additions.Add(new ArchiveAddition(entryName: entryName, fileSystemInfo: childFileSystemInfo)); + } + } + // Write a non-terminating error if a securityException occurs + catch (System.Security.SecurityException securityException) + { + var errorId = nameof(ErrorCode.InsufficientPermissionsToAccessPath); + var errorRecord = new ErrorRecord(securityException, errorId: errorId, ErrorCategory.SecurityError, targetObject: directoryInfo); + _cmdlet.WriteError(errorRecord); + } + } + + /// + /// Get the archive path from a fully qualified path + /// + /// A fully qualified path + /// + /// + private string GetEntryName(FileSystemInfo fileSystemInfo, out bool doesPreservePathStructure) + { + string entryName; + doesPreservePathStructure = false; + // If the path is relative to the current working directory, return the relative path as name + if (TryGetPathRelativeToCurrentWorkingDirectory(path: fileSystemInfo.FullName, out var relativePath)) + { + Debug.Assert(relativePath is not null); + doesPreservePathStructure = true; + entryName = relativePath; + } + // Otherwise, return the name of the directory or file + else + { + entryName = fileSystemInfo.Name; + } + + if (fileSystemInfo.Attributes.HasFlag(FileAttributes.Directory) && !entryName.EndsWith(Path.DirectorySeparatorChar)) + { + entryName += System.IO.Path.DirectorySeparatorChar; + } + + + return entryName; + } + + /// + /// Determines the entry name of a file or directory based on its path and a prefix. + /// The prefix is removed from the path and the remaining portion is the entry name. + /// + /// + /// + /// + /// + private static string GetEntryNameUsingPrefix(string path, string prefix) + { + if (prefix == string.Empty) return path; + + // If the path does not start with the prefix, throw an exception + if (!path.StartsWith(prefix)) + { + throw new ArgumentException($"{path} does not begin with {prefix}"); + } + + // If the path is the same length as the prefix + if (path.Length == prefix.Length) + { + throw new ArgumentException($"The length of {path} is shorter than or equal to the length of {prefix}"); + } + + string entryName = path.Substring(prefix.Length); + + return entryName; + } + + /// + /// Gets the prefix from the path to a directory. This prefix is necessary to determine the entry names of all + /// descendents of the directory. + /// + /// + /// + private static string GetPrefixForPath(System.IO.DirectoryInfo directoryInfo) + { + // Get the parent directory of the path + if (directoryInfo.Parent is null) + { + return string.Empty; + } + var prefix = directoryInfo.Parent.FullName; + if (!prefix.EndsWith(System.IO.Path.DirectorySeparatorChar)) + { + prefix += System.IO.Path.DirectorySeparatorChar; + } + return prefix; + } + + /// + /// Tries to get a path relative to the current working directory as long as the relative path does not contain ".." + /// + /// + /// + /// + private bool TryGetPathRelativeToCurrentWorkingDirectory(string path, out string? relativePathToWorkingDirectory) + { + Debug.Assert(!string.IsNullOrEmpty(path)); + string? workingDirectoryRoot = Path.GetPathRoot(_cmdlet.SessionState.Path.CurrentFileSystemLocation.Path); + string? pathRoot = Path.GetPathRoot(path); + if (workingDirectoryRoot != pathRoot) { + relativePathToWorkingDirectory = null; + return false; + } + string relativePath = Path.GetRelativePath(_cmdlet.SessionState.Path.CurrentFileSystemLocation.Path, path); + relativePathToWorkingDirectory = relativePath.Contains("..") ? null : relativePath; + return relativePathToWorkingDirectory is not null; + } + + internal System.Collections.ObjectModel.Collection? GetResolvedPathFromPSProviderPath(string path, HashSet nonexistentPaths) { + // Keep the exception at the top, then when an error occurs, use the exception to create an ErrorRecord + Exception? exception = null; + System.Collections.ObjectModel.Collection? fullyQualifiedPaths = null; + try + { + // Resolve path + var resolvedPaths = _cmdlet.SessionState.Path.GetResolvedProviderPathFromPSPath(path, out var providerInfo); + + // If the path is from the filesystem, set it to fullyQualifiedPaths so it can be returned + // Otherwise, create an exception so an error will be written + if (providerInfo.Name != FileSystemProviderName) + { + var exceptionMsg = ErrorMessages.GetErrorMessage(ErrorCode.InvalidPath); + exception = new ArgumentException(exceptionMsg); + } else { + fullyQualifiedPaths = resolvedPaths; + } + } + catch (System.Management.Automation.ProviderNotFoundException providerNotFoundException) + { + exception = providerNotFoundException; + } + catch (System.Management.Automation.DriveNotFoundException driveNotFoundException) + { + exception = driveNotFoundException; + } + catch (System.Management.Automation.ProviderInvocationException providerInvocationException) + { + exception = providerInvocationException; + } + catch (System.Management.Automation.PSNotSupportedException notSupportedException) + { + exception = notSupportedException; + } + catch (System.Management.Automation.PSInvalidOperationException invalidOperationException) + { + exception = invalidOperationException; + } + // If a path can't be found, write an error + catch (System.Management.Automation.ItemNotFoundException) + { + nonexistentPaths.Add(path); + } + + // If an exception was caught, write a non-terminating error + if (exception is not null) + { + var errorRecord = new ErrorRecord(exception: exception, errorId: nameof(ErrorCode.InvalidPath), errorCategory: ErrorCategory.InvalidArgument, + targetObject: path); + _cmdlet.WriteError(errorRecord); + } + + return fullyQualifiedPaths; + } + + // Resolves a literal path. Does not check if the path exists. + // If an exception occurs with a provider, it throws a terminating error + internal string? GetUnresolvedPathFromPSProviderPath(string path) { + // Keep the exception at the top, then when an error occurs, use the exception to create an ErrorRecord + Exception? exception = null; + string? fullyQualifiedPath = null; + try + { + // Resolve path + var resolvedPath = _cmdlet.SessionState.Path.GetUnresolvedProviderPathFromPSPath(path, out var providerInfo, out var psDriveInfo); + + // If the path is from the filesystem, set fullyQualifiedPath to resolvedPath so it can be returned + // Otherwise, create an exception so an error will be written + if (providerInfo.Name != FileSystemProviderName) + { + var exceptionMsg = ErrorMessages.GetErrorMessage(ErrorCode.InvalidPath); + exception = new ArgumentException(exceptionMsg); + } + else + { + fullyQualifiedPath = resolvedPath; + } + } + catch (System.Management.Automation.ProviderNotFoundException providerNotFoundException) + { + exception = providerNotFoundException; + } + catch (System.Management.Automation.DriveNotFoundException driveNotFoundException) + { + exception = driveNotFoundException; + } + catch (System.Management.Automation.ProviderInvocationException providerInvocationException) + { + exception = providerInvocationException; + } + catch (System.Management.Automation.PSNotSupportedException notSupportedException) + { + exception = notSupportedException; + } + catch (System.Management.Automation.PSInvalidOperationException invalidOperationException) + { + exception = invalidOperationException; + } + + // If an exception was caught, write a non-terminating error of throwError == false. Otherwise, throw a terminating errror + if (exception is not null) + { + var errorRecord = new ErrorRecord(exception: exception, errorId: nameof(ErrorCode.InvalidPath), errorCategory: ErrorCategory.InvalidArgument, + targetObject: path); + _cmdlet.ThrowTerminatingError(errorRecord); + } + + return fullyQualifiedPath; + } + + // Resolves a literal path. If it does not exist, it adds the path to nonexistentPaths. + // If an exception occurs with a provider, it writes a non-terminating error + internal string? GetUnresolvedPathFromPSProviderPath(string path, HashSet nonexistentPaths) { + // Keep the exception at the top, then when an error occurs, use the exception to create an ErrorRecord + Exception? exception = null; + string? fullyQualifiedPath = null; + try + { + // Resolve path + var resolvedPath = _cmdlet.SessionState.Path.GetUnresolvedProviderPathFromPSPath(path, out var providerInfo, out var psDriveInfo); + + // If the path is from the filesystem, set fullyQualifiedPath to resolvedPath so it can be returned + // Otherwise, create an exception so an error will be written + if (providerInfo.Name != FileSystemProviderName) + { + var exceptionMsg = ErrorMessages.GetErrorMessage(ErrorCode.InvalidPath); + exception = new ArgumentException(exceptionMsg); + } + // If the path does not exist, create an exception + else if (!Path.Exists(resolvedPath)) { + nonexistentPaths.Add(resolvedPath); + } + else + { + fullyQualifiedPath = resolvedPath; + } + } + catch (System.Management.Automation.ProviderNotFoundException providerNotFoundException) + { + exception = providerNotFoundException; + } + catch (System.Management.Automation.DriveNotFoundException driveNotFoundException) + { + exception = driveNotFoundException; + } + catch (System.Management.Automation.ProviderInvocationException providerInvocationException) + { + exception = providerInvocationException; + } + catch (System.Management.Automation.PSNotSupportedException notSupportedException) + { + exception = notSupportedException; + } + catch (System.Management.Automation.PSInvalidOperationException invalidOperationException) + { + exception = invalidOperationException; + } + + // If an exception was caught, write a non-terminating error + if (exception is not null) + { + var errorRecord = new ErrorRecord(exception: exception, errorId: nameof(ErrorCode.InvalidPath), errorCategory: ErrorCategory.InvalidArgument, + targetObject: path); + _cmdlet.WriteError(errorRecord); + } + + return fullyQualifiedPath; + } + } +} diff --git a/src/TarArchive.cs b/src/TarArchive.cs new file mode 100644 index 0000000..da25815 --- /dev/null +++ b/src/TarArchive.cs @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Formats.Tar; +using System.IO; +using System.Text; + +namespace Microsoft.PowerShell.Archive +{ + internal class TarArchive : IArchive + { + private bool disposedValue; + + private readonly ArchiveMode _mode; + + private readonly string _path; + + private readonly TarWriter _tarWriter; + + private readonly FileStream _fileStream; + + ArchiveMode IArchive.Mode => _mode; + + string IArchive.Path => _path; + + public TarArchive(string path, ArchiveMode mode, FileStream fileStream) + { + _mode = mode; + _path = path; + _tarWriter = new TarWriter(archiveStream: fileStream, format: TarEntryFormat.Pax, leaveOpen: false); + _fileStream = fileStream; + } + + void IArchive.AddFileSystemEntry(ArchiveAddition entry) + { + _tarWriter.WriteEntry(fileName: entry.FileSystemInfo.FullName, entryName: entry.EntryName); + } + + string[] IArchive.GetEntries() + { + throw new NotImplementedException(); + } + + void IArchive.Expand(string destinationPath) + { + throw new NotImplementedException(); + } + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + // TODO: dispose managed state (managed objects) + _tarWriter.Dispose(); + _fileStream.Dispose(); + } + + // TODO: free unmanaged resources (unmanaged objects) and override finalizer + // TODO: set large fields to null + disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/WriteMode.cs b/src/WriteMode.cs new file mode 100644 index 0000000..00efad3 --- /dev/null +++ b/src/WriteMode.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.PowerShell.Archive +{ + public enum WriteMode + { + Create, + Update, + Overwrite + } +} diff --git a/src/ZipArchive.cs b/src/ZipArchive.cs new file mode 100644 index 0000000..4c74147 --- /dev/null +++ b/src/ZipArchive.cs @@ -0,0 +1,129 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO.Compression; +using System.Text; + +namespace Microsoft.PowerShell.Archive +{ + internal class ZipArchive : IArchive + { + private bool _disposedValue; + + private readonly ArchiveMode _mode; + + private readonly string _archivePath; + + private readonly System.IO.FileStream _archiveStream; + + private readonly System.IO.Compression.ZipArchive _zipArchive; + + private readonly System.IO.Compression.CompressionLevel _compressionLevel; + + private const char ZipArchiveDirectoryPathTerminator = '/'; + + ArchiveMode IArchive.Mode => _mode; + + string IArchive.Path => _archivePath; + + public ZipArchive(string archivePath, ArchiveMode mode, System.IO.FileStream archiveStream, CompressionLevel compressionLevel) + { + _disposedValue = false; + _mode = mode; + _archivePath = archivePath; + _archiveStream = archiveStream; + _zipArchive = new System.IO.Compression.ZipArchive(stream: archiveStream, mode: ConvertToZipArchiveMode(_mode), leaveOpen: true); + _compressionLevel = compressionLevel; + } + + // If a file is added to the archive when it already contains a folder with the same name, + // it is up to the extraction software to deal with it (this is how it's done in other archive software). + // The .NET API differentiates a file and folder based on the last character being '/'. In other words, if the last character in a path is '/', it is treated as a folder. + // Otherwise, the .NET API treats the path as a file. + void IArchive.AddFileSystemEntry(ArchiveAddition addition) + { + if (_mode == ArchiveMode.Extract) throw new InvalidOperationException("Cannot add a filesystem entry to an archive in read mode"); + + var entryName = addition.EntryName.Replace(System.IO.Path.DirectorySeparatorChar, System.IO.Path.AltDirectorySeparatorChar); + + // If the archive has an entry with the same name as addition.EntryName, then get it, so it can be replaced if necessary + System.IO.Compression.ZipArchiveEntry? entryInArchive = null; + if (_mode != ArchiveMode.Create) + { + // TODO: Add exception handling for _zipArchive.GetEntry + entryInArchive = _zipArchive.GetEntry(entryName); + } + + // If the addition is a folder, only create the entry in the archive -- nothing else is needed + if (addition.FileSystemInfo.Attributes.HasFlag(System.IO.FileAttributes.Directory)) + { + // If the archive does not have an entry with the same name, then add an entry for the directory + if (entryInArchive == null) + { + // Ensure addition.entryName has '/' at the end + if (!entryName.EndsWith(ZipArchiveDirectoryPathTerminator)) + { + entryName += ZipArchiveDirectoryPathTerminator; + } + + _zipArchive.CreateEntry(entryName); + } + } + else + { + // If the archive already has an entry with the same name as addition.EntryName, delete it + if (entryInArchive != null) + { + entryInArchive.Delete(); + } + + // TODO: Add exception handling + _zipArchive.CreateEntryFromFile(sourceFileName: addition.FileSystemInfo.FullName, entryName: entryName, compressionLevel: _compressionLevel); + } + } + + string[] IArchive.GetEntries() + { + throw new NotImplementedException(); + } + + void IArchive.Expand(string destinationPath) + { + throw new NotImplementedException(); + } + + private static System.IO.Compression.ZipArchiveMode ConvertToZipArchiveMode(ArchiveMode archiveMode) + { + switch (archiveMode) + { + case ArchiveMode.Create: return System.IO.Compression.ZipArchiveMode.Create; + case ArchiveMode.Update: return System.IO.Compression.ZipArchiveMode.Update; + case ArchiveMode.Extract: return System.IO.Compression.ZipArchiveMode.Read; + default: return System.IO.Compression.ZipArchiveMode.Update; + } + } + + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + _zipArchive.Dispose(); + _archiveStream.Dispose(); + } + + _disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +}