Copy-ToModulesDirectory.psm1
#requires -version 5 <# .SYNOPSIS Copy modules to the PowerShell Module Directory .DESCRIPTION Makes it a bit easier to copy the modules you are working on, to the PowerShell modules directory on the same machine. It tries to fix the most annoying problems that I have encountered during use, such as missing paths, writing restrictions, name conflict, absent manifest file etc. Short alias "ctmd" is also available. .INPUTS None .OUTPUTS None .EXAMPLE PS C:\> Copy-ToModulesDirectory -Directory "Stop-CpuEaters" -Force True PS C:\> Copy-ToModulesDirectory -Directory "Join-Object" True PS C:\> Copy-ToModulesDirectory -Directory "Write-List", "ConvertTo-Object", "Invoke-DownloadFile" -Force True True True #> function Write-ModuleDirectoryStructure { write-host write-host "A valid module has to have a structure:" write-host write-host " SomeModule - a directory, containing two mandatory files with matching names" write-host " |-----SomeModule.psm1 - a script, containing the module logic and ending with 'Export-ModuleMember' command" write-host " +-----SomeModule.psd1 - a manifest, containing the metadata of the module" write-host } function Write-ManifestCreationHelp { param( [string]$RelativePsdPath, [string]$RelativePsmPath, [string]$ModuleBaseName ) $username = $ENV:USERNAME write-host write-host "You can create the manifest with a command like:" write-host " New-ModuleManifest -Path $RelativePsdPath -RootModule $RelativePsmPath -FunctionsToExport $ModuleBaseName -Author '$username'" write-host } function CreateManifestFile { param( [string]$PsdPath, [string]$PsmPath, [string]$ModuleBaseName ) write-debug "Going to create the manifest" $username = $ENV:USERNAME $company = $ENV:COMPUTERNAME $copyright = "(c) {0} {1}. All rights reserved." -f (get-date -Format yyyy), $username $description = "This manifest was created automatically by Copy-ToModulesDirectory module" New-ModuleManifest -Path $PsdPath -RootModule $PsmPath -Author $username -CompanyName $company -Copyright $copyright -FunctionsToExport $ModuleBaseName -Description $description } function Copy-ToModulesDirectory { [alias("ctmd")] [CmdletBinding()] param( [Parameter(Position = 1, Mandatory)] [string[]]$Directory, [switch]$Force ) begin { # obtain the user's modules directory to use as the target $modulesSystemDirectory = $ENV:PSModulePath -split ';' | Where-Object { $_ -like "$home*" } | Select-Object -first 1 # if the user's modules directory is not accessible, get the one in Program Files if ($modulesSystemDirectory.count -eq 0) { write-debug "The User's profile has no PowerShell Modules Directories registered" $modulesSystemDirectory = $ENV:PSModulePath -split ';' | Where-Object { $_ -ne '$env:ProgramFiles*' } | Select-Object -first 1 if ($modulesSystemDirectory.count -eq 0) { write-debug "No PowerShell Modules Directories registered in '$env:ProgramFiles'" $modulesSystemDirectory = $ENV:PSModulePath -split ';' | Select-Object -first 1 } } if ($modulesSystemDirectory.count -eq 0) { write-host write-host "No PowerShell Modules Directories were found" write-host throw "The system critical path is missing" } # is it accessible? if (-not(test-path $modulesSystemDirectory -PathType Container)) { # try to create it mkdir $modulesSystemDirectory -Force -ErrorAction Continue if (-not $?) { Write-Host write-host "PowerShell Modules Directory doesn't exist AND couldn't be created" Write-Host throw "The system critical path doesn't exist" } } # is it writable? $testPath = Join-Path $modulesSystemDirectory "test.txt" out-file -InputObject "test" -FilePath $testPath -ErrorAction Continue if (-not $?) { write-host write-host "Write Access test failed: can't create a file in the directory '$modulesSystemDirectory'" write-host throw "The system critical directory is not writable" } Remove-Item $testPath -Force write-debug "PowerShell Modules Directory: $modulesSystemDirectory" } process { foreach ($d in $Directory) { write-debug "LOOP STARTED: $d" # is it a directory? if (-not (test-path $d -PathType Container)) { write-host write-host "You must specify a path to the module's directory, not the file or anything else." Write-ModuleDirectoryStructure throw "The path is invalid" } $sourceDirectory = Resolve-Path $d | Select-Object -ExpandProperty Path write-debug "Resolved path: $sourceDirectory" # are there the module files with matching names? $moduleParentDirectory = Split-Path $sourceDirectory -Parent $moduleBaseName = Split-Path $sourceDirectory -Leaf $psmName = "$moduleBaseName.psm1" $psmPath = join-path $sourceDirectory $psmName $relativePsmPath = resolve-path $psmPath -Relative -ErrorAction Ignore if (-not (test-path $psmPath -Type Leaf)) { write-host write-host "The file '$psmName' was expected in the directory '$sourceDirectory'" Write-ModuleDirectoryStructure throw "A mandatory file is missing" } $psdName = "$moduleBaseName.psd1" $psdPath = join-path $Directory $psdName $relativePsdPath = resolve-path $psdPath -Relative -ErrorAction Ignore if (-not (test-path $psdPath -Type Leaf)) { write-debug "No manifest at '$relativePsdPath'" if ($Force) { # create the manifest for the user CreateManifestFile -PsdPath $psdPath -PsmPath $psmPath -ModuleBaseName $moduleBaseName } if (-not (test-path $psdPath -Type Leaf)) { if ($Force) { write-debug "It seems my attempt to generate the manifest file has failed." } # give a hint and fail write-host write-host "A module must have a manifest file, but I failed to create it." Write-ManifestCreationHelp throw "The manifest file is missing" } } # does the target directory contain a subdir with the same name? $targetDirectory = join-path $modulesSystemDirectory $moduleBaseName $targetTempName = "{0}-{1}" -f $moduleBaseName, (Get-Date -Format HH-mm-ss-fff) $targetTempDirectory = join-path $modulesSystemDirectory $targetTempName if (test-path ($targetDirectory) -Type Container) { if (-not $Force) { write-host write-host "The directory '$targetDirectory' already exists." write-host write-host "Possible solutions:" write-host "1. Delete it using the command:" write-host " Remove-Item -Recurse -Path $targetDirectory" write-host write-host "2. Run this command again with the -Force argument:" write-host " Copy-ToModulesDirectory -Directory $d -Force" write-host throw "The target folder already exists" } else { write-debug "PowerShell Modules Directory already has a directory named '$moduleBaseName'" write-debug "FORCE MODE: going to fix it" write-debug "Rename '$moduleBaseName' => '$targetTempName'" Move-Item $targetDirectory $targetTempDirectory -Force -ErrorAction Continue if (-not $?) { Remove-Item $targetTempDirectory -Recurse -Force -ErrorAction Stop write-host write-host "Failed to rename the directory '$targetDirectory'." write-host write-host "Possible fixes:" write-host "a. Check the user rights" write-host "b. Try to execute this command in elevated environment (as Administrator)" write-host "c. Ensure this directory is not opened in editors etc." write-host "d. Check the path for invalid characters" write-host throw "Failed to rename a folder" } else { write-debug "The conflicting directory has been renamed to '$targetTempDirectory'" } } } # finally, copy it write-debug "Copy the module '$moduleBaseName' to the PowerShell Module Directory" Copy-Item $sourceDirectory $modulesSystemDirectory -Recurse -ErrorAction Continue if ($?) { write-debug "Copied successfully" # if the copy succeed, and there is a temp directory exists, remove it if (test-path $targetTempDirectory -type Container) { write-debug "Delete the directory $targetTempDirectory" Remove-Item $targetTempDirectory -Recurse -Force -ErrorAction Continue if (-not $?) { write-host write-host "Failed to remove the temporary folder '$targetTempDirectory'" Write-Host throw "Failed to delete a folder" } } } else { write-debug "The copy has failed" # if the copy failed: remove debris, rename the temp folder back if (test-path ($targetDirectory) -Type Container) { write-debug "Removing unfinished files" Remove-Item $targetDirectory -Recurse -Force -ErrorAction Stop } if (test-path ($targetTempDirectory) -Type Container) { write-debug "Restoring the previous contents" Remove-Item $targetDirectory -Recurse -Force -ErrorAction Stop } } write-debug "Return True if '$moduleBaseName' is found in PowerShell Modules Directory; otherwise return False" Test-Path $targetDirectory -PathType Container -ErrorAction Continue } #foreach } # process } # if not specified, all functions will be exported Export-ModuleMember -Function 'Copy-ToModulesDirectory' -Alias "ctmd" |