PowerShell: Add every sub-directory to path - powershell

On Windows 10 and via PowerShell, how do you add every sub-directory to the PATH variable?
I fine some method for Linux (e.g., this one), and "adapting" them to PowerShell, I came across the following.
'$env:PATH = $env:PATH + (find C:\Users\appveyor\ -type d -printf "\";%pbuild\\"")'
This throws The filename, directory name, or volume label syntax is incorrect.
A bit more context: I am running this on appveyor, and trying to add every path under appveyor (simplified for clarity) such as C:\Users\appveyor\*\build\ to path.

The find command on Linux is very different from the Find command on Windows.
In your case, I would use Get-ChildItem in PowerShell (which can also be used via the aliases gci, dir, ls), to return an array of directory objects, added to the current path casted as an array, and then -join them with the Windows path separator, which is a semicolon ;.
That might look like this:
$env:PATH = (
#($env:PATH) + (
Get-ChildItem -LiteralPath 'C:\Users\appveyor' -Directory -Recurse
)
) -join [System.IO.Path]::PathSeparator # <- that could just be a literal string ';'

$env:PATH =
#($env:PATH) +
[string]::join(
";",
(Get-ChildItem -LiteralPath "[PATH TO SEARCH]" -Directory -Recurse).fullname
)
This based on #briantist's suggestion; the main difference is in the join syntax.
To get this work on appveyor, do the following:
{ps:
"$env:PATH =
#($env:PATH) + [string]::join(
\";\",
(Get-ChildItem -LiteralPath \"[PATH TO SEARCH]\" -Directory -Recurse).fullname
)"
}

Related

Powershell Change path in variable

i need you help again :D
I have created a function to put the error logs in a file who take the name of my script (i call multiples scripts so it's very helpful), here is my function :
function ExportLog{
$path = Get-Location
$LogFile = [io.path]::ChangeExtension($MyInvocation.ScriptName,"log")
Write-Host $LogFile
$timestamps = Get-Date
$string_err = $_ | Out-String
$ExportError = "[" + $timestamps.DateTime + "]`n" + $string_err + "`n"| Out-File -FilePath $LogFile -Append
Read-Host “Appuyez sur ENTRER pour quitter...”}
This works fine but the log file created or edited is in the path of my script.
My question is how can i add \log\ in the path who is in my variable $LogFile ?
I tried to use Join-Path, but it just add path like this : C:\import\Modif_CSV.log\Logs ... I wan't to add the Logs folder before the name of the file ^^
Ty for help :)
You can split the current script filename from the full path and change the extension with:
$LogFileName = [IO.Path]::ChangeExtension((Split-Path $PSCommandPath -Leaf), 'log')
Next combine the current script path with the subfolder 'log' and with the new filename
$LogFullName = [IO.Path]::Combine($PSScriptRoot, 'log', $LogFileName)
Theo's helpful answer shows a .NET API-based solution that works in both Windows PowerShell and PowerShell (Core) 7+.
Here's a PowerShell (Core) 7+ solution that showcases new features (relative to Windows PowerShell):
$dir, $name = $PSCommandPath -split '\\', -2
Join-Path $dir log ((Split-Path -LeafBase $name) + '.log')
-split '\\', -2 splits the path into two strings by \: the last \-separated token, preceded by everything before the last \, thereby effectively splitting a file path into its directory path and file name. That is, -split now accepts a negative number as the count of tokens to return, with -$n meaning: return $n-1 tokens from the right of the input string (albeit in left-to-right order), and save any remaining part of the string in the return array's first element; e.g., 'a/b/c/d' -split '/', -3 yields 'a/b', 'c', 'd'
Split-Path -LeafBase returns a file path's file-name base, i.e. the file name without its extension.
Join-Path now accepts an open-ended number of child paths to join to the parent path; e.g., Join C:\ dir subdir now works to create C:\dir\subdir, whereas in Windows PowerShell you had to nest calls: Join-Path (Join-Path C:\ dir) subdir
Note: It would be handy if Split-Path supported returning all components of a given path in a single operation; GitHub issue #6606 proposes an -All switch that returns an object whose properties reflect all the constituent parts of the path, which would enable the following simplified solution:
# WISHFUL THINKING, as of PowerShell 7.2
$pathInfo = Split-Path -All $PSCommandPath
Join-Path $pathInfo.Parent log ($pathInfo.LeafBase + '.log')

Handling strings (path to files) with wildcard in Powershell

I'm writing a PS script that looks for specific files in different directory.
My code looks like this
# $Path is provided by the user, it's a path like
# $Path = "C:\Some Directory\Project [ABC]\Files\"
# There's a check to ensure path ends with a backslash
$PDFFiles = Get-Item $($Path + "*.pdf")
for ($counter=0; $counter -lt $PDFFiles.Length; $counter++) {
# Do stuff
}
The issue is that $Path may have character considered as wildcard (eg [ or ] in my example), but I can't use -LitteralPath because I need the *.pdf wildcard to be interpreted.
How to properly handle strings to tell PShell that this part is litteral, and this one is a wildcard?
Use Get-ChildItem instead of Get-Item.
Pass the path of the folder to -LiteralPath and then use the -Filter parameter for the wildcard file name filter:
$PDFFiles = Get-ChildItem -LiteralPath $Path -Filter "*.pdf"
-LiteralPath will not attempt to expand wildcard sequences in the path
An alternative approach is to escape the $Path value:
$escapedPath = [WildcardPattern]::Escape($Path)
Get-Item -Path (Join-Path $escapedPath *.pdf)

Windows cmd command for stripping versions from filenames?

Need Windows cmd command to rename files to names without version numbers, e.g.:
filename.exa.1 => filename.exa
filename_a.exb.23 => filename_a.exb
filename_b.exc.4567 => filename_b.exc
Filenames are variable in number of characters, and the primary extension is always 3 characters.
I once had a Solaris script "stripv" to accomplish this. I could enter "stripv *" in a directory and get a nice clean set of non-versioned files. If the command would result in duplicate filenames because multiple versions exist, then it would just skip the operation altogether.
TIA
Don't know how to do it in CMD, but here is some Powershell that would work for you:
# Quick way to get an array of filenames. You could also create a proper array,
# or read each line into an array from a file.
$filepaths = #"
C:\full\path\to\filename.exa.1
C:\full\path\to\filename_a.exb.23
\\server\share\path\to\filename_b.exc.4567
"# -Split "`n"
# For each path in $filepaths
$filepaths | Foreach-Object {
$path = $_
# Split-Path -Leaf gets only the filename
# -Replace expression just means to match on the ".number" at the end of the
# filename and replace it with an empty string (effectively removing it)
$newFilename = ( Split-Path -Leaf $path ) -Replace '\.\d+$', ''
# Warning output
Write-Warning "Renaming '${path}' to '${newFilename}'"
# Rename the file to the new name
Rename-Item -Path $path -NewName $newFilename
}
Basically, this code creates an array of full paths to files. For each path, it strips the filename from the full path and replaces the .number pattern at the end with nothing, which removes it from the filename. Now that we have the new filename, we use Rename-Item to rename the file to the new name.
Supply the folder name to this script block's $Folder variable, and it will enumerate the items within that folder, locate the last '.' character within the file name, and rename it as everything prior to the '.'.
E.g.: Filename.123.wrcrw.txt.123 would be renamed as Filename.123.wrcrw.txt or in your case, your files would lose the extraneous characters from the final '.' onwards. If the new name for the file already exists, it will write a warning stating that it could not rename the file, and continue on without trying.
$Folder = "C:\ProgramData\Temp"
Get-ChildItem -Path $Folder | Foreach {
$NewName = $_.Name.Substring(0,$_.Name.LastIndexOf('.'))
IF (!(Test-Path $Folder\$NewName))
{
Rename-Item $Folder\$_ -NewName $NewName
}
Else
{
Write-Warning "$($_.Name) cannot be renamed, $NewName already exists."
}
}
This should effectively mimic the behaviour you described for stripv *. This could easily be turned into a function with name stripv and added to your PowerShell profile to make it available at the command-line interactively and used in the same way as your Solaris script.

A positional parameter cannot be found that accepts argument '\'

I am trying to get the meta data from a directory and I am getting an error that A positional parameter cannot be found that accepts argument '\'. Not sure how to correct this?
$FileMetadata = Get-FileMetaData -folder (Get-childitem $Folder1 + "\" + $System.Name + "\Test" -Recurse -Directory).FullName
You need to do the concatenation in a subexpression:
$FileMetadata = Get-FileMetaData -folder (Get-childitem ($Folder1 + "\" + $System.Name + "\Test") -Recurse -Directory).FullName
or embed the variables in a string like this:
$FileMetadata = Get-FileMetaData -folder (Get-childitem "$Folder1\$($System.Name)\Test" -Recurse -Directory).FullName
The most robust way in Powershell to build a path when parts of the path are stored in variables is to use the cmdlet Join-Path.
This also eliminate the need to use "\".
So in your case, it would be :
$FoldersPath = Join-Path -Path $Folder1 -ChildPath "$System.Name\Test"
$FileMetadata = Get-FileMetaData -folder (Get-ChildItem $FoldersPath -Recurse -Directory).FullName
If you come from the world of VBScript. With Powershell, every space is interpreted as completely separate parameter being passed to the cmdlet. You need to either place the formula in parentheses to have the formula evaluated prior to passing it as the path parameter or enclosing with quotes also works:
Wont work, Powershell thinks this is two parameters:
$Folder1 + "\" + $System.Name
Will work with brackets:
($Folder1 + "\" + $System.Name)
Will also work together when enclosed in quotes:
"$Folder1\$System.Name"
Ref.
I was trying to run the sc create <name> binPath=... command and kept getting the positional character issue. I resolved this by running the command in Command Prompt not PowerShell. Ugh. 😐

Powershell Copying files with varying folders in the path

Right up front apologies for my lack of knowledge with Powershell. Very new to the language . I need to copy some files located in a certain path to another similar path. For example:
C:\TEMP\Users\<username1>\Documents\<varyingfoldername>\*
C:\TEMP\Users\<username2>\Documents\<varyingfoldername>\*
C:\TEMP\Users\<username3>\Documents\<varyingfoldername>\*
C:\TEMP\Users\<username4>\Documents\<varyingfoldername>\*
etc....
to
C:\Files\Users\<username1>\Documents\<varyingfoldername>\*
C:\Files\Users\<username2>\Documents\<varyingfoldername>\*
C:\Files\Users\<username3>\Documents\<varyingfoldername>\*
C:\Files\Users\<username4>\Documents\<varyingfoldername>\*
etc....
So basically all files and directories from path one need to be copied to the second path for each one of the different paths. The only known constant is the first part of the path like C:\TEMP\Users...... and the first part of the destination like C:\Files\Users.....
I can get all the different paths and files by using:
gci C:\TEMP\[a-z]*\Documents\[a-z]*\
but I am not sure how to then pass what's found in the wildcards so I can use them when I do the copy. Any help would be appreciated here.
This should work:
Get-ChildItem "C:\TEMP\*\Documents\*" | ForEach-Object {
$old = $_.FullName
$new = $_.FullName.Replace("C:\TEMP\Users\","C:\Files\Users\")
Move-Item $old $new
}
For additional complexity in matching folder levels, something like this should work:
Get-ChildItem "C:\TEMP\*\Documents\*" -File | ForEach-Object {
$old = $_.FullName
$pathArray = $old.Split("\") # Splits the path into an array
$new = [system.String]::Join("\", $pathArray[0..1]) # Creates a starting point, in this case C:\Temp
$new += "\" + $pathArray[4] # Appends another folder level, you can change the index to match the folder you're after
$new += "\" + $pathArray[6] # You can repeat this line to keep matching different folders
Copy-Item -Recurse -Force $old $new
}