From 3be8c38b0a2e424ce7608a4c4abaa1dd63cf4c63 Mon Sep 17 00:00:00 2001 From: Maxime Kjaer Date: Fri, 13 Dec 2024 12:32:42 -0800 Subject: [PATCH] Add formatter to RestSetAcls (#243) * Refactor build tools into modules * Add PowerShell formatting test * Format all RestSetAcls code * Remove lint.ps1 * Fix github actions job title * Add recommendation to configure format-on-save --- .github/workflows/RestSetAcls.yml | 23 ++- RestSetAcls/CONTRIBUTING.md | 52 +++++- RestSetAcls/RestSetAcls/PrintUtils.ps1 | 23 +-- RestSetAcls/RestSetAcls/RestSetAcls.psd1 | 191 ++++++++++++----------- RestSetAcls/RestSetAcls/RestSetAcls.psm1 | 137 ++++++++-------- RestSetAcls/RestSetAcls/SddlUtils.ps1 | 21 +-- RestSetAcls/build-tools.psm1 | 72 +++++++++ RestSetAcls/build.depend.psd1 | 12 +- RestSetAcls/check-manifest.ps1 | 1 - RestSetAcls/init.ps1 | 33 ++-- RestSetAcls/lint.ps1 | 8 - RestSetAcls/publish-local.ps1 | 47 ------ RestSetAcls/publish-psgallery.ps1 | 15 -- RestSetAcls/publish-tools.psm1 | 102 ++++++++++++ RestSetAcls/test.ps1 | 1 - RestSetAcls/test/TestSetup.ps1 | 22 ++- RestSetAcls/unpublish-local.ps1 | 32 ---- 17 files changed, 473 insertions(+), 319 deletions(-) create mode 100644 RestSetAcls/build-tools.psm1 delete mode 100644 RestSetAcls/check-manifest.ps1 delete mode 100644 RestSetAcls/lint.ps1 delete mode 100644 RestSetAcls/publish-local.ps1 delete mode 100644 RestSetAcls/publish-psgallery.ps1 create mode 100644 RestSetAcls/publish-tools.psm1 delete mode 100644 RestSetAcls/test.ps1 delete mode 100644 RestSetAcls/unpublish-local.ps1 diff --git a/.github/workflows/RestSetAcls.yml b/.github/workflows/RestSetAcls.yml index d67431ca..531898c3 100644 --- a/.github/workflows/RestSetAcls.yml +++ b/.github/workflows/RestSetAcls.yml @@ -23,8 +23,23 @@ jobs: - name: Run all Pester tests run: | .\init.ps1 - .\test.ps1 - + Test + + test-format: + name: Test PowerShell formatting + runs-on: windows-latest + defaults: + run: + working-directory: .\RestSetAcls + shell: pwsh + steps: + - name: Check out repository code + uses: actions/checkout@v4 + - name: Test PowerShell formatting + run: | + .\init.ps1 + Test-Format + Check-manifest: name: Check .psd1 manifest file runs-on: windows-latest @@ -37,7 +52,7 @@ jobs: - name: Check .psd1 file with Test-ModuleManifest run: | .\init.ps1 - .\check-manifest.ps1 + Test-Manifest lint-with-PSScriptAnalyzer: name: Install and run PSScriptAnalyzer @@ -51,4 +66,4 @@ jobs: - name: Lint with PSScriptAnalyzer run: | .\init.ps1 - .\lint.ps1 + Lint diff --git a/RestSetAcls/CONTRIBUTING.md b/RestSetAcls/CONTRIBUTING.md index f1dca3b4..ab2bf496 100644 --- a/RestSetAcls/CONTRIBUTING.md +++ b/RestSetAcls/CONTRIBUTING.md @@ -4,16 +4,54 @@ > These instructions are only meant for contributors to this project. > If you want to use the script, refer to the README. +## Editor configuration + +### VS Code + +This module has PR validations that enforce formatting rules, so it's recommended to configure VS Code to format on save, to ensure that your changes are always formatted correctly. + ## Installing development dependencies ```powershell .\init.ps1 ``` -## Running unit tests +## Testing + +### Run unit tests + +```powershell +Test +``` + +### Test formatting + +```powershell +Test-Format +``` + +### Test module manifest + +```powershell +Test-Manifest +``` + +### Test all the above + +```powershell +Test-All +``` + +## Format files ```powershell -.\test.ps1 +Format +``` + +## Lint + +```powershell +Lint ``` ## Publishing the module locally @@ -21,12 +59,14 @@ This is a useful test before publishing to the PSGallery. ```powershell -.\publish-local.ps1 +Import-Module .\publish-tools.psm1 + +Publish-Local Install-Module RestSetAcls -Repository LocalRepo Uninstall-Module RestSetAcls -.\unpublish-local.ps1 +Unpublish-Local ``` ## Publishing the module to the PSGallery @@ -36,7 +76,9 @@ Uninstall-Module RestSetAcls 1. Run the following command: ```powershell - .\publish-psgallery.ps1 -apiKey "" + Import-Module .\publish-tools.psm1 + + Publish-PSGallery -apiKey "" ``` ## Setting up an E2E test run diff --git a/RestSetAcls/RestSetAcls/PrintUtils.ps1 b/RestSetAcls/RestSetAcls/PrintUtils.ps1 index e783bab7..9221b4b8 100644 --- a/RestSetAcls/RestSetAcls/PrintUtils.ps1 +++ b/RestSetAcls/RestSetAcls/PrintUtils.ps1 @@ -1,11 +1,11 @@ -function Ask([Parameter(Mandatory=$false)][string] $question) -{ +function Ask([Parameter(Mandatory = $false)][string] $question) { while ($true) { $yn = Read-Host "${question} [Y/n]" $yn = $yn.Trim().ToLower() if ($yn -eq 'n') { return $false - } elseif ($yn -eq '' -or $yn -eq 'y') { + } + elseif ($yn -eq '' -or $yn -eq 'y') { return $true } Write-Host "Invalid answer '$yn'. Answer with either 'y' or 'n'" -ForegroundColor Red @@ -35,7 +35,8 @@ function Write-DoneHeader { if (Get-SpecialCharactersPrintable) { $checkmark = [System.Char]::ConvertFromUtf32([System.Convert]::ToInt32("2713", 16)) Write-Host "($checkmark) Done: " -ForegroundColor Green -NoNewline - } else { + } + else { Write-Host "Done: " -ForegroundColor Green -NoNewline } } @@ -44,7 +45,8 @@ function Write-PartialHeader { if (Get-SpecialCharactersPrintable) { $cross = [System.Char]::ConvertFromUtf32([System.Convert]::ToInt32("2717", 16)) Write-Host "($cross) Partial: " -ForegroundColor Yellow -NoNewline - } else { + } + else { Write-Host "Partial: " -ForegroundColor Yellow -NoNewline } } @@ -53,7 +55,8 @@ function Write-FailedHeader { if (Get-SpecialCharactersPrintable) { $cross = [System.Char]::ConvertFromUtf32([System.Convert]::ToInt32("2717", 16)) Write-Host "($cross) Failed: " -ForegroundColor Red -NoNewline - } else { + } + else { Write-Host "Failed: " -ForegroundColor Red -NoNewline } } @@ -62,17 +65,18 @@ function Write-WarningHeader { if (Get-SpecialCharactersPrintable) { $warning = [System.Char]::ConvertFromUtf32([System.Convert]::ToInt32("26A0", 16)) Write-Host "($warning) Warning: " -ForegroundColor Yellow -NoNewline - } else { + } + else { Write-Host "Warning: " -ForegroundColor Yellow -NoNewline } } function Write-Failure { param ( - [Parameter(Mandatory=$true, Position=0)] + [Parameter(Mandatory = $true, Position = 0)] [string]$Overview, - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [string]$Details = $null ) @@ -83,3 +87,4 @@ function Write-Failure { Write-Host $Details -ForegroundColor Red } } + diff --git a/RestSetAcls/RestSetAcls/RestSetAcls.psd1 b/RestSetAcls/RestSetAcls/RestSetAcls.psd1 index f4552828..096766e4 100644 --- a/RestSetAcls/RestSetAcls/RestSetAcls.psd1 +++ b/RestSetAcls/RestSetAcls/RestSetAcls.psd1 @@ -8,141 +8,142 @@ @{ -# Script module or binary module file associated with this manifest. -RootModule = 'RestSetAcls.psm1' + # Script module or binary module file associated with this manifest. + RootModule = 'RestSetAcls.psm1' -# Version number of this module. -ModuleVersion = '0.1.2' + # Version number of this module. + ModuleVersion = '0.1.2' -# Supported PSEditions -# CompatiblePSEditions = @() + # Supported PSEditions + # CompatiblePSEditions = @() -# ID used to uniquely identify this module -GUID = '1b9ea644-06b1-47d4-8da4-f8758a43fc49' + # ID used to uniquely identify this module + GUID = '1b9ea644-06b1-47d4-8da4-f8758a43fc49' -# Author of this module -Author = 'Microsoft Corporation' + # Author of this module + Author = 'Microsoft Corporation' -# Company or vendor of this module -CompanyName = 'Microsoft Corporation' + # Company or vendor of this module + CompanyName = 'Microsoft Corporation' -# Copyright statement for this module -Copyright = '(c) Microsoft Corporation. All rights reserved.' + # Copyright statement for this module + Copyright = '(c) Microsoft Corporation. All rights reserved.' -# Description of the functionality provided by this module -Description = 'Set file permissions on an Azure Files share using REST API' + # Description of the functionality provided by this module + Description = 'Set file permissions on an Azure Files share using REST API' -# Minimum version of the PowerShell engine required by this module -PowerShellVersion = '5.1' + # Minimum version of the PowerShell engine required by this module + PowerShellVersion = '5.1' -# Name of the PowerShell host required by this module -# PowerShellHostName = '' + # Name of the PowerShell host required by this module + # PowerShellHostName = '' -# Minimum version of the PowerShell host required by this module -# PowerShellHostVersion = '' + # Minimum version of the PowerShell host required by this module + # PowerShellHostVersion = '' -# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. -# DotNetFrameworkVersion = '' + # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. + # DotNetFrameworkVersion = '' -# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. -# ClrVersion = '' + # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. + # ClrVersion = '' -# Processor architecture (None, X86, Amd64) required by this module -# ProcessorArchitecture = '' + # Processor architecture (None, X86, Amd64) required by this module + # ProcessorArchitecture = '' -# Modules that must be imported into the global environment prior to importing this module -RequiredModules = @( - @{ - ModuleName = "Az.Storage" - GUID = "dfa9e4ea-1407-446d-9111-79122977ab20" - ModuleVersion = "6.0.0" - } -) + # Modules that must be imported into the global environment prior to importing this module + RequiredModules = @( + @{ + ModuleName = "Az.Storage" + GUID = "dfa9e4ea-1407-446d-9111-79122977ab20" + ModuleVersion = "6.0.0" + } + ) -# Assemblies that must be loaded prior to importing this module -# RequiredAssemblies = @() + # Assemblies that must be loaded prior to importing this module + # RequiredAssemblies = @() -# Script files (.ps1) that are run in the caller's environment prior to importing this module. -# ScriptsToProcess = @() + # Script files (.ps1) that are run in the caller's environment prior to importing this module. + # ScriptsToProcess = @() -# Type files (.ps1xml) to be loaded when importing this module -# TypesToProcess = @() + # Type files (.ps1xml) to be loaded when importing this module + # TypesToProcess = @() -# Format files (.ps1xml) to be loaded when importing this module -# FormatsToProcess = @() + # Format files (.ps1xml) to be loaded when importing this module + # FormatsToProcess = @() -# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess -# NestedModules = @() + # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess + # NestedModules = @() -# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. -FunctionsToExport = @( - "Set-AzureFilesAclRecursive" -) + # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. + FunctionsToExport = @( + "Set-AzureFilesAclRecursive" + ) -# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. -CmdletsToExport = @() + # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. + CmdletsToExport = @() -# Variables to export from this module -VariablesToExport = '*' + # Variables to export from this module + VariablesToExport = '*' -# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. -AliasesToExport = @() + # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. + AliasesToExport = @() -# DSC resources to export from this module -# DscResourcesToExport = @() + # DSC resources to export from this module + # DscResourcesToExport = @() -# List of all modules packaged with this module -# ModuleList = @() + # List of all modules packaged with this module + # ModuleList = @() -# List of all files packaged with this module -# FileList = @() + # List of all files packaged with this module + # FileList = @() -# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. -PrivateData = @{ + # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. + PrivateData = @{ - PSData = @{ + PSData = @{ - # Tags applied to this module. These help with module discovery in online galleries. - # See: https://learn.microsoft.com/en-us/powershell/gallery/concepts/publishing-guidelines?view=powershellget-3.x#tag-your-package-with-the-compatible-pseditions-and-platforms - Tags = @( - "Azure", - "Storage", - "ACL", - "PSEdition_Desktop", # Packages that are compatible with Windows PowerShell - "PSEdition_Core", # Packages that are compatible with PowerShell 6 and higher - "Windows" # Packages that are compatible with the Windows Operating System - ) + # Tags applied to this module. These help with module discovery in online galleries. + # See: https://learn.microsoft.com/en-us/powershell/gallery/concepts/publishing-guidelines?view=powershellget-3.x#tag-your-package-with-the-compatible-pseditions-and-platforms + Tags = @( + "Azure", + "Storage", + "ACL", + "PSEdition_Desktop", # Packages that are compatible with Windows PowerShell + "PSEdition_Core", # Packages that are compatible with PowerShell 6 and higher + "Windows" # Packages that are compatible with the Windows Operating System + ) - # A URL to the license for this module. - LicenseUri = 'https://raw.githubusercontent.com/Azure-Samples/azure-files-samples/master/LICENSE.md' + # A URL to the license for this module. + LicenseUri = 'https://raw.githubusercontent.com/Azure-Samples/azure-files-samples/master/LICENSE.md' - # A URL to the main website for this project. - ProjectUri = 'https://github.com/Azure-Samples/azure-files-samples' + # A URL to the main website for this project. + ProjectUri = 'https://github.com/Azure-Samples/azure-files-samples' - # A URL to an icon representing this module. - # IconUri = '' + # A URL to an icon representing this module. + # IconUri = '' - # ReleaseNotes of this module - # ReleaseNotes = '' + # ReleaseNotes of this module + # ReleaseNotes = '' - # Prerelease string of this module - # Prerelease = '' + # Prerelease string of this module + # Prerelease = '' - # Flag to indicate whether the module requires explicit user acceptance for install/update/save - # RequireLicenseAcceptance = $false + # Flag to indicate whether the module requires explicit user acceptance for install/update/save + # RequireLicenseAcceptance = $false - # External dependent modules of this module - # ExternalModuleDependencies = @() + # External dependent modules of this module + # ExternalModuleDependencies = @() - } # End of PSData hashtable + } # End of PSData hashtable -} # End of PrivateData hashtable + } # End of PrivateData hashtable -# HelpInfo URI of this module -# HelpInfoURI = '' + # HelpInfo URI of this module + # HelpInfoURI = '' -# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. -# DefaultCommandPrefix = '' + # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. + # DefaultCommandPrefix = '' } + diff --git a/RestSetAcls/RestSetAcls/RestSetAcls.psm1 b/RestSetAcls/RestSetAcls/RestSetAcls.psm1 index d14fe582..4cc76e69 100644 --- a/RestSetAcls/RestSetAcls/RestSetAcls.psm1 +++ b/RestSetAcls/RestSetAcls/RestSetAcls.psm1 @@ -4,13 +4,13 @@ function Write-LiveFilesAndFoldersProcessingStatus { [OutputType([int])] param ( - [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [Object[]]$FileOrFolder, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [datetime]$StartTime, - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [int]$RefreshRateHertz = 10 ) @@ -65,16 +65,16 @@ function Write-LiveFilesAndFoldersProcessingStatus { function Write-FinalFilesAndFoldersProcessed { [OutputType([System.Void])] param ( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [int]$ProcessedCount, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [hashtable]$Errors, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [timespan]$TotalTime, - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [int]$MaxErrorsToShow = 10 ) @@ -125,7 +125,8 @@ function Write-FinalFilesAndFoldersProcessed { Write-Host "errors.json" -ForegroundColor Blue Write-Host - } else { + } + else { $itemsPerSec = [math]::Round($successCount / $TotalTime.TotalSeconds, 1) Write-DoneHeader @@ -142,10 +143,10 @@ function Write-FinalFilesAndFoldersProcessed { function Write-SddlWarning { param ( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$Sddl, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$NewSddl ) Write-WarningHeader @@ -168,19 +169,19 @@ function Write-SddlWarning { function Get-AzureFilesRecursive { param ( - [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [Microsoft.WindowsAzure.Commands.Common.Storage.ResourceModel.AzureStorageBase[]]$DirectoryContents, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [Microsoft.Azure.Commands.Common.Authentication.Abstractions.IStorageContext]$Context, - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [string]$DirectoryPath = "", - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [switch]$SkipFiles = $false, - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [switch]$SkipDirectories = $false ) @@ -212,7 +213,7 @@ function Get-AzureFilesRecursive { if (($isDirectory -and !$SkipDirectories) -or (!$isDirectory -and !$SkipFiles)) { Write-Output @{ FullPath = $fullPath - File = $file + File = $file } } } @@ -221,18 +222,18 @@ function Get-AzureFilesRecursive { function New-AzureFilePermission { [OutputType([string])] param ( - [Parameter(Mandatory=$true, HelpMessage="Azure storage context")] + [Parameter(Mandatory = $true, HelpMessage = "Azure storage context")] [Microsoft.Azure.Commands.Common.Authentication.Abstractions.IStorageContext]$Context, - [Parameter(Mandatory=$true, HelpMessage="Name of the file share")] + [Parameter(Mandatory = $true, HelpMessage = "Name of the file share")] [string]$FileShareName, [Parameter( - Mandatory=$true, - HelpMessage="File permission in the Security Descriptor Definition Language (SDDL). " + - "SDDL must have an owner, group, and discretionary access control list (DACL). " + - "The provided SDDL string format of the security descriptor should not have " + - "domain relative identifier (like 'DU', 'DA', 'DD' etc) in it.")] + Mandatory = $true, + HelpMessage = "File permission in the Security Descriptor Definition Language (SDDL). " + + "SDDL must have an owner, group, and discretionary access control list (DACL). " + + "The provided SDDL string format of the security descriptor should not have " + + "domain relative identifier (like 'DU', 'DA', 'DD' etc) in it.")] [string]$Sddl ) @@ -244,10 +245,10 @@ function New-AzureFilePermission { function Get-AzureFilePermission { [OutputType([string])] param ( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$PermissionKey, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [Microsoft.WindowsAzure.Commands.Common.Storage.ResourceModel.AzureStorageFileShare]$Share ) @@ -256,10 +257,10 @@ function Get-AzureFilePermission { function Set-AzureFilePermission { param ( - [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [Microsoft.WindowsAzure.Commands.Common.Storage.ResourceModel.AzureStorageBase]$File, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$SddlPermission ) @@ -269,7 +270,8 @@ function Set-AzureFilePermission { $null, # SmbProperties $SddlPermission # filePermission ) | Out-Null - } else { + } + else { $file = [Microsoft.WindowsAzure.Commands.Common.Storage.ResourceModel.AzureStorageFile]$File $file.ShareFileClient.SetHttpHeaders( $null, # newSize @@ -282,10 +284,10 @@ function Set-AzureFilePermission { function Set-AzureFilePermissionKey { param ( - [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [Microsoft.WindowsAzure.Commands.Common.Storage.ResourceModel.AzureStorageBase]$File, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$FilePermissionKey ) @@ -295,7 +297,8 @@ function Set-AzureFilePermissionKey { if ($File.GetType().Name -eq "AzureStorageFileDirectory") { $directory = [Microsoft.WindowsAzure.Commands.Common.Storage.ResourceModel.AzureStorageFileDirectory]$File $directory.ShareDirectoryClient.SetHttpHeaders($smbProperties) | Out-Null - } else { + } + else { $file = [Microsoft.WindowsAzure.Commands.Common.Storage.ResourceModel.AzureStorageFile]$File $file.ShareFileClient.SetHttpHeaders( $null, # newSize @@ -308,14 +311,15 @@ function Set-AzureFilePermissionKey { function Get-AzureFilePermissionKey { [OutputType([string])] param ( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [Microsoft.WindowsAzure.Commands.Common.Storage.ResourceModel.AzureStorageBase]$FileOrDirectory ) if ($FileOrDirectory.GetType().Name -eq "AzureStorageFileDirectory") { $directory = [Microsoft.WindowsAzure.Commands.Common.Storage.ResourceModel.AzureStorageFileDirectory]$FileOrDirectory return $directory.ShareDirectoryProperties.SmbProperties.FilePermissionKey - } else { + } + else { $file = [Microsoft.WindowsAzure.Commands.Common.Storage.ResourceModel.AzureStorageFile]$FileOrDirectory return $file.FileProperties.SmbProperties.FilePermissionKey } @@ -324,31 +328,31 @@ function Get-AzureFilePermissionKey { function Set-AzureFilesAclRecursive { [CmdletBinding()] param ( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [Microsoft.Azure.Commands.Common.Authentication.Abstractions.IStorageContext]$Context, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$FileShareName, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$FilePath, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$SddlPermission, - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [bool]$Parallel = $true, - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [int]$ThrottleLimit = 10, - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [switch]$SkipFiles = $false, - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [switch]$SkipDirectories = $false, - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [switch]$WriteToPipeline = $false ) @@ -366,7 +370,8 @@ function Set-AzureFilesAclRecursive { # Try to parse SDDL permission, check for common issues try { $securityDescriptor = ConvertTo-RawSecurityDescriptor -Sddl $SddlPermission - } catch { + } + catch { Write-Failure "SDDL permission is invalid" return } @@ -399,7 +404,8 @@ function Set-AzureFilesAclRecursive { # Setting permission key should in theory also be slightly faster than setting SDDL directly (though this may not be noticeable in practice). try { $filePermissionKey = New-AzureFilePermission -Context $Context -FileShareName $FileShareName -Sddl $SddlPermission - } catch { + } + catch { Write-Failure "Failed to create file permission" -Details $_.Exception.Message return } @@ -410,7 +416,8 @@ function Set-AzureFilesAclRecursive { # If it's a directory, Get-AzureFilesRecursive will get its contents. try { $directory = Get-AzStorageFile -Context $Context -ShareName $FileShareName -Path $FilePath -ErrorAction Stop - } catch { + } + catch { Write-Failure "Failed to read root directory" -Details $_.Exception.Message return } @@ -434,7 +441,8 @@ function Set-AzureFilesAclRecursive { $errorMessage = "" try { Set-AzureFilePermissionKey -File $_.File -FilePermissionKey $using:filePermissionKey - } catch { + } + catch { $success = $false $errorMessage = $_.Exception.Message } @@ -442,16 +450,17 @@ function Set-AzureFilesAclRecursive { # Write full output if requested, otherwise write minimal output if ($using:WriteToPipeline) { Write-Output @{ - Time = (Get-Date).ToString("o") - FullPath = $_.FullPath - Permission = $using:SddlPermission - Success = $success + Time = (Get-Date).ToString("o") + FullPath = $_.FullPath + Permission = $using:SddlPermission + Success = $success ErrorMessage = $errorMessage } - } else { + } + else { Write-Output @{ - FullPath = $_.FullPath - Success = $success + FullPath = $_.FullPath + Success = $success ErrorMessage = $errorMessage } } @@ -466,7 +475,8 @@ function Set-AzureFilesAclRecursive { } ` | Write-LiveFilesAndFoldersProcessingStatus -RefreshRateHertz 10 -StartTime $startTime ` | ForEach-Object { if ($WriteToPipeline) { Write-Output $_ } } - } else { + } + else { Get-AzureFilesRecursive ` -Context $Context ` -DirectoryContents @($directory) ` @@ -480,7 +490,8 @@ function Set-AzureFilesAclRecursive { # Set the ACL try { Set-AzureFilePermissionKey -File $_.File -FilePermissionKey $filePermissionKey - } catch { + } + catch { $success = $false $errorMessage = $_.Exception.Message $errors[$fullPath] = $errorMessage @@ -491,16 +502,17 @@ function Set-AzureFilesAclRecursive { # Write full output if requested, otherwise write minimal output if ($WriteToPipeline) { Write-Output @{ - Time = (Get-Date).ToString("o") - FullPath = $fullPath - Permission = $SddlPermission - Success = $success + Time = (Get-Date).ToString("o") + FullPath = $fullPath + Permission = $SddlPermission + Success = $success ErrorMessage = $errorMessage } - } else { + } + else { Write-Output @{ - FullPath = $fullPath - Success = $success + FullPath = $fullPath + Success = $success ErrorMessage = $errorMessage } } @@ -515,3 +527,4 @@ function Set-AzureFilesAclRecursive { Write-Host "`r" -NoNewline # Clear the line from the live progress reporting Write-FinalFilesAndFoldersProcessed -ProcessedCount $processedCount -Errors $errors -TotalTime $totalTime } + diff --git a/RestSetAcls/RestSetAcls/SddlUtils.ps1 b/RestSetAcls/RestSetAcls/SddlUtils.ps1 index 9052e192..05665a41 100644 --- a/RestSetAcls/RestSetAcls/SddlUtils.ps1 +++ b/RestSetAcls/RestSetAcls/SddlUtils.ps1 @@ -1,6 +1,6 @@ function ConvertTo-RawSecurityDescriptor { param ( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string]$sddl ) @@ -20,7 +20,7 @@ function ConvertTo-RawSecurityDescriptor { function ConvertFrom-RawSecurityDescriptor { param ( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [System.Security.AccessControl.RawSecurityDescriptor]$descriptor ) return $descriptor.GetSddlForm([System.Security.AccessControl.AccessControlSections]::All) @@ -28,13 +28,13 @@ function ConvertFrom-RawSecurityDescriptor { function Get-AllAceFlagsMatch { param ( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [System.Security.AccessControl.RawSecurityDescriptor]$SecurityDescriptor, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [System.Security.AccessControl.AceFlags]$EnabledFlags, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [System.Security.AccessControl.AceFlags]$DisabledFlags ) @@ -51,13 +51,13 @@ function Get-AllAceFlagsMatch { function Set-AceFlags { param ( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [System.Security.AccessControl.RawSecurityDescriptor]$SecurityDescriptor, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [System.Security.AccessControl.AceFlags]$EnableFlags, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [System.Security.AccessControl.AceFlags]$DisableFlags ) @@ -77,7 +77,8 @@ function Set-AceFlags { $_.SecurityIdentifier, $_.IsCallback, $_.GetOpaque()) - } else { + } + else { throw "Unsupported ACE type: $($_.GetType().Name)" } } @@ -91,4 +92,4 @@ function Set-AceFlags { for ($i = 0; $i -lt $newAces.Count; $i++) { $SecurityDescriptor.DiscretionaryAcl.InsertAce($i, $newAces[$i]) | Out-Null } -} \ No newline at end of file +} diff --git a/RestSetAcls/build-tools.psm1 b/RestSetAcls/build-tools.psm1 new file mode 100644 index 00000000..15d605cb --- /dev/null +++ b/RestSetAcls/build-tools.psm1 @@ -0,0 +1,72 @@ +function Get-PowerShellFiles { + Get-ChildItem $PSScriptRoot -Exclude bin ` + | Get-ChildItem -Recurse -Include *.psd1, *.psm1, *.ps1 +} + +function Lint { + param ( + [Parameter(Mandatory = $false)] + [string]$Path = "$PSScriptRoot\RestSetAcls" + ) + + Invoke-ScriptAnalyzer -Path $Path -ExcludeRule PSAvoidUsingWriteHost -Recurse -Outvariable issues + $errors = $issues.Where({ $_.Severity -eq 'Error' }) + $warnings = $issues.Where({ $_.Severity -eq 'Warning' }) + if ($errors) { + Write-Error "There were $($errors.Count) errors and $($warnings.Count) warnings total." -ErrorAction Stop + } + else { + Write-Output "There were $($errors.Count) errors and $($warnings.Count) warnings total." + } +} + +function Test-Format { + $failed = $false + + foreach ($file in Get-PowerShellFiles) { + $content = Get-Content -Path $file -Raw + $formatted = Invoke-Formatter -ScriptDefinition $content + + if ($content -ne $formatted) { + Write-Host "File $($file.FullName) is not formatted correctly." -ForegroundColor Red + $failed = $true + } + } + + if ($failed) { + Write-Error "There were formatting issues." -ErrorAction Stop + } + else { + Write-Output "There were no formatting issues." + } +} + +function Format { + foreach ($file in Get-PowerShellFiles) { + $content = Get-Content -Path $file -Raw + $formatted = Invoke-Formatter -ScriptDefinition $content + + if ($content -ne $formatted) { + Write-Host "Reformatting $file" -ForegroundColor Blue + Set-Content -Path $file -Value $formatted + } + else { + Write-Host "File $($file.FullName) is already formatted correctly." -ForegroundColor Green + } + } +} + +function Test { + Invoke-Pester -Path $PSScriptRoot\test -Output Detailed +} + +function Test-Manifest { + Test-ModuleManifest -Path $PSScriptRoot\RestSetAcls\RestSetAcls.psd1 +} + +function Test-All { + Test + Lint + Test-Manifest + Test-Format +} \ No newline at end of file diff --git a/RestSetAcls/build.depend.psd1 b/RestSetAcls/build.depend.psd1 index 8b10eb78..46a918f8 100644 --- a/RestSetAcls/build.depend.psd1 +++ b/RestSetAcls/build.depend.psd1 @@ -1,13 +1,13 @@ # This file lists out development requirements for the module. # For the runtime requirements, see the module manifest. @{ - PSDependOptions = @{ - Target = 'bin\Dependencies' - Install = $true + PSDependOptions = @{ + Target = 'bin\Dependencies' + Install = $true AddToPath = $true } - Pester = 'latest' + Pester = 'latest' PSScriptAnalyzer = 'latest' - 'Az.Storage' = '6.0.0' -} \ No newline at end of file + 'Az.Storage' = '6.0.0' +} diff --git a/RestSetAcls/check-manifest.ps1 b/RestSetAcls/check-manifest.ps1 deleted file mode 100644 index 7a68c5a3..00000000 --- a/RestSetAcls/check-manifest.ps1 +++ /dev/null @@ -1 +0,0 @@ -Test-ModuleManifest -Path $PSScriptRoot\RestSetAcls\RestSetAcls.psd1 diff --git a/RestSetAcls/init.ps1 b/RestSetAcls/init.ps1 index 531a79c1..f6758db4 100644 --- a/RestSetAcls/init.ps1 +++ b/RestSetAcls/init.ps1 @@ -1,15 +1,24 @@ -Write-Host "Checking if PSDepend is installed" -ForegroundColor White -$installed = $null -ne (Get-Module PSDepend -ListAvailable) - -if ($installed) { - Write-Host "Already installed" -} else { - Write-Host "Not installed" - Write-Host "`nInstalling PSDepend" -ForegroundColor White - Install-Module -Name PSDepend -Repository PSGallery -Force +function Init { + Write-Host "Checking if PSDepend is installed" -ForegroundColor White + $installed = $null -ne (Get-Module PSDepend -ListAvailable) + + if ($installed) { + Write-Host "Already installed" + } + else { + Write-Host "Not installed" + Write-Host "`nInstalling PSDepend" -ForegroundColor White + Install-Module -Name PSDepend -Repository PSGallery -Force + Write-Host "Done" + } + + Write-Host "`nInstalling build dependencies" -ForegroundColor White + Invoke-PSDepend -Path $PSScriptRoot\build.depend.psd1 -Force + Write-Host "Done" + + Write-Host "`nImporting build tools" -ForegroundColor White + Import-Module $PSScriptRoot\build-tools.psm1 -Force Write-Host "Done" } -Write-Host "`nInstalling build dependencies" -ForegroundColor White -Invoke-PSDepend -Path $PSScriptRoot\build.depend.psd1 -Force -Write-Host "Done" \ No newline at end of file +Init \ No newline at end of file diff --git a/RestSetAcls/lint.ps1 b/RestSetAcls/lint.ps1 deleted file mode 100644 index ae714d02..00000000 --- a/RestSetAcls/lint.ps1 +++ /dev/null @@ -1,8 +0,0 @@ -Invoke-ScriptAnalyzer -Path $PSScriptRoot\RestSetAcls -Severity Warning -Recurse -Outvariable issues -$errors = $issues.Where({$_.Severity -eq 'Error'}) -$warnings = $issues.Where({$_.Severity -eq 'Warning'}) -if ($errors) { - Write-Error "There were $($errors.Count) errors and $($warnings.Count) warnings total." -ErrorAction Stop -} else { - Write-Output "There were $($errors.Count) errors and $($warnings.Count) warnings total." -} diff --git a/RestSetAcls/publish-local.ps1 b/RestSetAcls/publish-local.ps1 deleted file mode 100644 index 532222a4..00000000 --- a/RestSetAcls/publish-local.ps1 +++ /dev/null @@ -1,47 +0,0 @@ -$repoName = "LocalRepo" -$RepoPath = "$PSScriptRoot\bin\LocalRepo" -$moduleName = "RestSetAcls" -$psd1 = Import-PowershellDataFile -Path "$PSScriptRoot\RestSetAcls\RestSetAcls.psd1" -$dependencies = $psd1.RequiredModules.ModuleName - -Write-Host "Creating $RepoPath" -ForegroundColor White -New-Item -Path $RepoPath -ItemType Directory -Force | Out-Null -Write-Host "Done" -ForegroundColor Gray - -Write-Host "`nRegistering $repoName with $RepoPath" -ForegroundColor White -Register-PSRepository -Name $repoName -SourceLocation $RepoPath -InstallationPolicy Trusted -Write-Host "Done" -ForegroundColor Gray - -Write-Host "`nPublishing $moduleName and dependencies in $repoName" -ForegroundColor White -# Check if dependencies are installed -foreach ($dependency in $dependencies) { - $modules = Get-Module $dependency -ListAvailable - if ($modules.Length -eq 0) { - throw "Module $dependency not found in the current environment. Please run .\init.ps1 first." - } -} - -# Publish dependencies -foreach ($dependency in $dependencies) { - $module = $(Get-Module $dependency -ListAvailable)[0] - Write-Host "Publishing $dependency v$($module.Version) to $repoName" -ForegroundColor Gray - $modulePath = Get-Item $module.Path - Publish-Module -Path $modulePath.Directory.FullName -Repository $repoName -} - -# Publish main module -Write-Host "Publishing $moduleName to $repoName" -ForegroundColor Gray -Publish-Module -Path $PSScriptRoot/$moduleName -Repository $repoName - -Write-Host "Done" -ForegroundColor Gray - -Write-Host "`nUnloading currently loaded modules" -ForegroundColor White -Remove-Module -Name $moduleName -Force -ErrorAction SilentlyContinue -foreach ($dependency in $dependencies) { - Remove-Module -Name $dependency -Force -ErrorAction SilentlyContinue -} -Write-Host "Done" -ForegroundColor Gray - -Write-Host "`nInstalling $moduleName from $repoName" -ForegroundColor White -Install-Module -Name $moduleName -Repository $repoName -Force -Write-Host "Done" -ForegroundColor Gray diff --git a/RestSetAcls/publish-psgallery.ps1 b/RestSetAcls/publish-psgallery.ps1 deleted file mode 100644 index aa930fd4..00000000 --- a/RestSetAcls/publish-psgallery.ps1 +++ /dev/null @@ -1,15 +0,0 @@ -[CmdletBinding(SupportsShouldProcess)] -param ( - [Parameter(Mandatory=$true, HelpMessage="PowerShell Gallery API Key")] - [string]$apiKey -) - - -Write-Host "Running tests" -ForegroundColor White -NoNewline -Invoke-Pester -Path $PSScriptRoot\test -Output Minimal -Write-Host - -# This will also run Test-ModuleManifest -Write-Host "Publishing" -ForegroundColor White -Publish-Module -Path $PSScriptRoot\RestSetAcls -NuGetApiKey $apiKey -WhatIf:$WhatIfPreference -Write-Host "Done" -ForegroundColor Green \ No newline at end of file diff --git a/RestSetAcls/publish-tools.psm1 b/RestSetAcls/publish-tools.psm1 new file mode 100644 index 00000000..2cfafb22 --- /dev/null +++ b/RestSetAcls/publish-tools.psm1 @@ -0,0 +1,102 @@ +$repoName = "LocalRepo" +$repoPath = "$PSScriptRoot\bin\LocalRepo" +$moduleName = "RestSetAcls" +$psd1 = "$PSScriptRoot\RestSetAcls\RestSetAcls.psd1" + +function Get-Dependencies { + return (Import-PowershellDataFile -Path $psd1).RequiredModules.ModuleName +} + +function Publish-Local { + $dependencies = Get-Dependencies + + Write-Host "Creating $RepoPath" -ForegroundColor White + New-Item -Path $RepoPath -ItemType Directory -Force | Out-Null + Write-Host "Done" -ForegroundColor Gray + + Write-Host "`nRegistering $repoName with $RepoPath" -ForegroundColor White + Register-PSRepository -Name $repoName -SourceLocation $RepoPath -InstallationPolicy Trusted + Write-Host "Done" -ForegroundColor Gray + + Write-Host "`nPublishing $moduleName and dependencies in $repoName" -ForegroundColor White + # Check if dependencies are installed + foreach ($dependency in $dependencies) { + $modules = Get-Module $dependency -ListAvailable + if ($modules.Length -eq 0) { + throw "Module $dependency not found in the current environment. Please run .\init.ps1 first." + } + } + + # Publish dependencies + foreach ($dependency in $dependencies) { + $module = $(Get-Module $dependency -ListAvailable)[0] + Write-Host "Publishing $dependency v$($module.Version) to $repoName" -ForegroundColor Gray + $modulePath = Get-Item $module.Path + Publish-Module -Path $modulePath.Directory.FullName -Repository $repoName + } + + # Publish main module + Write-Host "Publishing $moduleName to $repoName" -ForegroundColor Gray + Publish-Module -Path $PSScriptRoot/$moduleName -Repository $repoName + + Write-Host "Done" -ForegroundColor Gray + + Write-Host "`nUnloading currently loaded modules" -ForegroundColor White + Remove-Module -Name $moduleName -Force -ErrorAction SilentlyContinue + foreach ($dependency in $dependencies) { + Remove-Module -Name $dependency -Force -ErrorAction SilentlyContinue + } + Write-Host "Done" -ForegroundColor Gray + + Write-Host "`nInstalling $moduleName from $repoName" -ForegroundColor White + Install-Module -Name $moduleName -Repository $repoName -Force + Write-Host "Done" -ForegroundColor Gray +} + +function Uninstall-LocalRepoModule { + param ( + [string]$moduleName, + [string]$repoName + ) + Get-InstalledModule -Name $moduleName | Where-Object { $_.Repository -eq $repoName } | ForEach-Object { + Write-Host "Uninstalling $moduleName v$($_.Version) from $repoName" -ForegroundColor Gray + Uninstall-Module -Name $moduleName -RequiredVersion $_.Version -Force + } +} + +function Unpublish-Local { + $dependencies = Get-Dependencies + + Write-Host "Unloading modules" -ForegroundColor White + Remove-Module -Name $moduleName -Force -ErrorAction SilentlyContinue + $dependencies | ForEach-Object { Remove-Module -Name $_ -Force -ErrorAction SilentlyContinue } + Write-Host "Done" -ForegroundColor Gray + + Write-Host "`nUninstalling $moduleName" -ForegroundColor White + Uninstall-LocalRepoModule -moduleName $moduleName -repoName $repoName + Write-Host "Done" -ForegroundColor Gray + + Write-Host "`nUnregistering LocalRepo" -ForegroundColor White + Unregister-PSRepository -Name LocalRepo + + Write-Host "`nRemoving LocalRepo" -ForegroundColor White + Remove-Item -Path $RepoPath -Recurse -Force + Write-Host "Done" -ForegroundColor Gray +} + +function Publish-PSGallery { + [CmdletBinding(SupportsShouldProcess)] + param ( + [Parameter(Mandatory = $true, HelpMessage = "PowerShell Gallery API Key")] + [string]$apiKey + ) + + Write-Host "Running tests" -ForegroundColor White -NoNewline + Invoke-Pester -Path $PSScriptRoot\test -Output Minimal + Write-Host + + # This will also run Test-ModuleManifest + Write-Host "Publishing" -ForegroundColor White + Publish-Module -Path $PSScriptRoot\RestSetAcls -NuGetApiKey $apiKey -WhatIf:$WhatIfPreference + Write-Host "Done" -ForegroundColor Green +} diff --git a/RestSetAcls/test.ps1 b/RestSetAcls/test.ps1 deleted file mode 100644 index aa2ef83f..00000000 --- a/RestSetAcls/test.ps1 +++ /dev/null @@ -1 +0,0 @@ -Invoke-Pester -Path $PSScriptRoot\test -Output Detailed \ No newline at end of file diff --git a/RestSetAcls/test/TestSetup.ps1 b/RestSetAcls/test/TestSetup.ps1 index 1d91298f..6d06276e 100644 --- a/RestSetAcls/test/TestSetup.ps1 +++ b/RestSetAcls/test/TestSetup.ps1 @@ -1,5 +1,4 @@ -function New-Arborescence -{ +function New-Arborescence { [CmdletBinding(SupportsShouldProcess)] param ( [Microsoft.WindowsAzure.Commands.Storage.AzureStorageContext]$context, @@ -10,18 +9,17 @@ function New-Arborescence [int]$Depth ) - if ($Depth -eq 0) - { + if ($Depth -eq 0) { # Create file - for ($j = 1; $j -le $NumberFilesPerDir; $j++) - { + for ($j = 1; $j -le $NumberFilesPerDir; $j++) { $fileName = "file-$j.txt" $filePath = $BasePath + "/" + $fileName $localFilePath = Join-Path -Path $env:TEMP -ChildPath $fileName if ($WhatIfPreference) { Write-Host "WhatIf: Creating file $filePath" - } else { + } + else { Write-Host "Creating file $filePath" # Create file locally @@ -44,16 +42,15 @@ function New-Arborescence } } } - else - { - for ($i = 1; $i -le $NumberDirs; $i++) - { + else { + for ($i = 1; $i -le $NumberDirs; $i++) { # Create dir $dirPath = "${BasePath}/dir-$i" if ($WhatIfPreference) { Write-Host "WhatIf: Creating dir $dirPath" - } else { + } + else { Write-Host "Creating dir $dirPath" New-AzStorageDirectory ` -Context $Context ` @@ -73,3 +70,4 @@ function New-Arborescence } } } + diff --git a/RestSetAcls/unpublish-local.ps1 b/RestSetAcls/unpublish-local.ps1 deleted file mode 100644 index 0bbc6f06..00000000 --- a/RestSetAcls/unpublish-local.ps1 +++ /dev/null @@ -1,32 +0,0 @@ -$repoName = "LocalRepo" -$repoPath = "$PSScriptRoot\bin\LocalRepo" -$moduleName = "RestSetAcls" -$psd1 = Import-PowershellDataFile -Path "$PSScriptRoot\RestSetAcls\RestSetAcls.psd1" -$dependencies = $psd1.RequiredModules.ModuleName - -function Uninstall-LocalRepoModule { - param ( - [string]$moduleName, - [string]$repoName - ) - Get-InstalledModule -Name $moduleName | Where-Object { $_.Repository -eq $repoName } | ForEach-Object { - Write-Host "Uninstalling $moduleName v$($_.Version) from $repoName" -ForegroundColor Gray - Uninstall-Module -Name $moduleName -RequiredVersion $_.Version -Force - } -} - -Write-Host "Unloading modules" -ForegroundColor White -Remove-Module -Name $moduleName -Force -ErrorAction SilentlyContinue -$dependencies | ForEach-Object { Remove-Module -Name $_ -Force -ErrorAction SilentlyContinue } -Write-Host "Done" -ForegroundColor Gray - -Write-Host "`nUninstalling $moduleName" -ForegroundColor White -Uninstall-LocalRepoModule -moduleName $moduleName -repoName $repoName -Write-Host "Done" -ForegroundColor Gray - -Write-Host "`nUnregistering LocalRepo" -ForegroundColor White -Unregister-PSRepository -Name LocalRepo - -Write-Host "`nRemoving LocalRepo" -ForegroundColor White -Remove-Item -Path $RepoPath -Recurse -Force -Write-Host "Done" -ForegroundColor Gray \ No newline at end of file