How to deal with paths on PowerShell? - powershell

What is the best practice approach to deal with paths to scripts/folders on PowerShell ?
Example 1 (relative to the target script location) :
$SettingsModule = (Get-Item -Path ('{0}\..\Settings\SettingsReader.psm1' -f $PSScriptRoot)).FullName
$AzureSqlUtilsModule = (Get-Item -Path ('{0}\UtilsSqlAzure.psm1' -f $PSScriptRoot)).FullName
# code here ...
Example 2 (path is informed via parameter):
$PfxPath = (Join-Path -Path $SecureRepoRoot -ChildPath $appCredentials.PfxPath)
# code here ...
Or should the common paths to my local folders/repositories/scripts be handle with environment variables ? Or is there a better alternative ?
I have a Linux background, and ideally I would use environment variables to define a common paths, however I'm not sure if this is a best approach on Windows/PowerShell.

Depends on what you are doing. I would go for relative paths as of powershells modularity. When you move or update a module, you give all relevant resources with it. I don't like external dependencies that much, so I stick with that approach. Env might not be present or vary on systems - the path of your module or script is always clear.

Related

Using Module and path

I am running into some issues with loading a PSM1 file. The PSM1 is always in the same folder as the PS1, but that folder can change. It works if I use a literal path like this...
Using module '\\Mac\iCloud Drive\Px Tools 4.#\Dev 4.0\#Spikes\Windows7\library.psm1'
But that's useless since the code could be installed anywhere. And the code is signed, so it can't change.
It also doesn't work to use the new (in PS3.0) $PSScriptRoot automatic variable...
Using module "$PSScriptRoot\library.psm1"
Nor does a relative path or simple file name, as in...
Using module ".\library.psm1"
or...
Using module "library.psm1"
What am I missing, other than perhaps it's time to call it a day?
Also, note that the library contains classes, so other module loading options like Import-Module don't work. This has me wondering if perhaps classes are better supported in a later version, and I should really be targeting PS 6.0, rather than 5.1?
Looking for more info on your problem, I came around this blog post. It says relative paths work, so have you tried using relative path with single quotes or without quotes?
Since values inside double-quotes get evaluated before passing them to the cmdlet, using them might not work.
you should run import-module first.
in PS1, I have add code to call import-module
like this:
ForEach($_ in Get-ChildItem "$env:TEMP\*.psm1") {
$checkModuleName = $_.Name -replace ".psm1"
$importModule = $_.Name
if (Get-Module $checkModuleName) {
Write-Host "Update Module $importModule" -ForegroundColor Green
Remove-Module $checkModuleName
Import-Module "$env:TEMP\$importModule"
}
else {
Write-Host "Import Module $importModule" -ForegroundColor Green
Import-Module "$env:TEMP\$importModule"
}
}
when import done, I can used all module.

Use Powershell to delete file from a folder set by windows registry value

Sorry if this has been answered before, I have tirelessly searched and cannot find the exact answer I am a Mac user and have not ventured into Windows registry before.
I am creating an installer for audio plugins and have a separate demo version of the titles. In the registry for the demo version, I have the entry
Demo=1
and also have paths set for various components of the plugins, which are optionally installed, these are stored in the registry as the user may install these into different directories depending on their host software
VST3=C:\Somepath\VST3
VST32=C\AnotherPath\VSTPlugins
VST64=C\AnotherPathAgain\VSTPlugins
I have found how to search the registry to check if Demo=1
$val = Get-ItemProperty -Path hklm:software\Audio Vitamins\Structure -Name “Demo”
if($val.Demo -eq 1)
{
**** This is where I need help *****
}
How do I set Powershell to remove a particular file 'structure.vst3' from the path set in VST3 or or 'structure.dll' from the paths set in VST32 and VST64. Note these can all be present or only 1 of them depending on the original install.
You have a couple of different paths (pun intended) you can take here.
You can organize your "demo = 1" files into one folder and the others in another and reference the demo folder location in the registry. Then you get the file location and use del to remove them. Much easier and doesn't require you to track which ones are there with unnecessary registry entries.
$demofolder = gp -path HKLM:\path\to\registry\key -Name "demofolder"
dir $demofolder -file|%{del $_ -force}

Internal functions in PowerShell Module

I'm writing a module, and I have some helper functions that I don't want exposed, but I do want available to module functions internally. I have set up my directory structure like:
root\
..Private
....Invoke-PrivateTest.ps1
....private.psd1
....private.psm1
..Public
....Get-Something.ps1
....Public.psd1
....Public.psm1
..test.psd1
I've setup a repository on github https://github.com/jpbruckler/test that has all the module files in it.
The behavior that I'm expecting is that Get-Something is a public function. When running Get-Command -Module Test it should be listed. On the contrary, Invoke-PrivateTest should not be in the output of that command.
When calling Get-Something it should output the text Invoke-PrivateTest called. Instead, I get an error stating that the command Invoke-PrivateTest doesn't exist.
I am explicitly saying in test.psd1 that only the Get-Something function is to be exported.
Both the Private module and the public module are being called via the NestedModules property in test.psd1. Any help or pointers would be appreciated.
Unless you have other reasons to put the code into separate (sub)modules I'd keep it in one folder and control what's exported via the function names. Use "official" notation (<Verb>-<Noun>) for the names of public functions and omit the hyphen in the names of private function (<Verb><Noun>). That way you can export public functions in your global .psd1 like this:
FunctionsToExport = '*-*'
#PetSerAl pointed me in the right direction. Ultimately this came down to a scoping issue. The way I had the module arranged, each sub-module would need to make a call to load the private module, which is a bunch of code duplication - and also what I was hoping to avoid by splitting out some helper functions.
To get it all to work, instead of multiple sub modules, I just broke up the Public folder into sub folders that will hold scripts that do similar things, basically removing all the .psd1 and .psm1 files from the Public directory. I did the same thing for the Private directory. This left me with a bunch of loose .ps1 files that I load in test.psm1 with the following code:
$Private = (Get-ChildItem -Path (Join-Path $PSScriptRoot 'Private') -Filter *.ps1)
$Public = (Get-ChildItem -Path (Join-Path $PSScriptRoot 'Public') -Filter *.ps1 -Recurse)
foreach ($Script in $Public) {
. $Script.FullName
Export-ModuleMember $Script.BaseName
}
foreach ($Script in $Private) {
. $Script.FullName
}
I've modified the test module at https://github.com/jpbruckler/test to reflect the changes I made.

What is the way to escape colon in powershell?

I have the following powershell in a script file:
cd "$env:systemdrive:\$env:APPDATA\Mozilla\Firefox\Profiles\*.default"
Expanded, I want this to do something similar to this:
cd "C:\Users\Bob\AppData\Roaming\Mozilla\Firefox\Profiles\f1wkii3l.default"
But when I run it, I get:
cd : Cannot find drive. A drive with the name '\C' does not exist.
I am guessing the colon I put in there to be at the end of C:\ is causing problems.
I tried:
cd "${env:systemdrive}:\${env:APPDATA}\Mozilla\Firefox\Profiles\*.default"
But then I get the error:
cd : Cannot find a provider with the name 'C'.
How can I escape the colon so that powershell just sees it as normal text?
NOTE: I looked at this question: Escaping a colon in powershell and the answer is all about .NET and does not answer my question (though the question is very similar).
Other answers are valid for troubleshooting and for your task at hand. One suggestion I have, and what I consider a best-practice, is making use of Join-Path anytime you are dealing with paths so you don't have to worry about trailing or beginning path separators.
This example
$d1 = "${env:APPDATA}\Mozilla\Firefox\Profiles\*.default"
$d2 = Join-Path ${env:APPDATA} "\Mozilla\Firefox\Profiles\*.default"
$d3 = Join-Path ${env:APPDATA} "Mozilla\Firefox\Profiles\*.default"
Write-Output "d1 = '$d1'"
Write-Output "d2 = '$d2'"
Write-Output "d3 = '$d3'"
Produces
d1 = 'C:\Users\xxx\AppData\Roaming\Mozilla\Firefox\Profiles\*.default'
d2 = 'C:\Users\xxx\AppData\Roaming\Mozilla\Firefox\Profiles\*.default'
d3 = 'C:\Users\xxx\AppData\Roaming\Mozilla\Firefox\Profiles\*.default'
All are acceptable, and I would tend to use the d3 version in my own scripts.
You don't need to escape anything there. The APPDATA environment variable already includes the drive, so you only need
cd "${env:APPDATA}\Mozilla\Firefox\Profiles\*.default"
${env:systemdrive}:\${env:APPDATA} would create a path C:\C:\Users\..., which is indeed invalid.
If you write-host those environmental variables, you'll see that:
PS C:\>write-host $env:systemdrive
C:
PS C:\>write-host $env:appdata
C:\Users\****\AppData\Roaming
So your current attempt expands to C:C:\Users\****\AppData\Roaming\...
So all you need is the command:
cd "$env:Appdata\Mozilla\Firefox\Profiles\*.default"

Get the path of the importing script from within a module?

Is there a way to get the path of a script that imported a module from within that module?
The script module I'm writing is meant to load settings from files relative to the importing script. I plan on reusing the module for a number of projects, so I would prefer if the module could make no assumptions about where its being imported from.
This is a nice to have, it would be great the module could be as implicit as possible. If all else fails though, I can just have the caller pass in its location.
Unfortunately everything I've attempted so far returns the path to the module (not what imported it). Here's a simple demonstration:
Test-RelativeModule.ps1, Stored at: c:\test\
import-module "$PSScriptRoot\mod\Test.psm1"
Test.psm1, Stored at: c:\test\mod\
# returns 'c:\test\mod'
write-host "`$PSScriptRoot: $PSScriptRoot"
# returns 'c:\test\mod'
# The value of $MyInvocation.MyCommand.Path is 'c:\test\mod\Test.psm1'
write-host "Split Invoation: $(Split-Path $MyInvocation.MyCommand.Path)"
# returns whatever path the console is currently residing
write-host "Resolve Path: $((resolve-path '.\').path)"
# what I'm looking for is something to return 'c:\test' from within the module
# without making any assumptions about the folder structure
Try this:
Write-Host "My invoker's PSScriptRoot: $($MyInvocation.PSScriptRoot)"