Defining path to a file with $PSScriptRoot - powershell

This is not exactly asking for help, more like kind of curiosity. I have a path to a file defined like this in my script:
$RootDir = $PSScriptRoot
$ExcelFile = $RootDir + "\" + $File + ".xlsx"
The $File variable is defined earlier in the script. This version works perfectly fine, but when I tried to simplify it like this to a single row, it didn't work:
$ExcelFile = $PSScriptRoot + "\" + $File + ".xlsx"
Any idea why the second version does not work? It says that the file could not be found. I can't see any logical reason for it.

For building paths use the Join-Path cmdlet:
$ExcelFile = Join-Path $PSScriptRoot ($File + '.xlsx')
In your particular case you could also go with a simple string with variables:
$ExcelFile = "${PSScriptRoot}\${File}.xlsx"
But Join-Path is the more resilient approach, because it automatically takes care of the path separator between the elements.

Thought this might help someone
# Get Current working path
if($PSScriptRoot){$PSRootPath = $PSScriptRoot}Else{Try { $PSRootPath = (Split-Path $psISE.CurrentFile.FullPath -Parent)} Catch {$PSRootPath = (Split-Path $MyInvocation.MyCommand.Path -Parent)}}

Related

How Get-Childitem and only include subfolders and files?

I have a script, and currently I do the following, which gets the full path to the files in the subdir:
$filenameOut = "out.html"
#get current working dir
$cwd = Get-ScriptDirectory
#get files to display in lists
$temp = Join-Path $cwd "Initial Forms"
$temp = Join-Path $temp "General Forms"
$InitialAppointmentGenArr = Get-ChildItem -Path $temp
So this will return a list where the first file in the array looks like this:
"//server/group/Creds/Documents/Initial Forms/General Forms/Background Check.pdf"
However, to have my generated web page work on our extranet, I can't give the full path to the file. I just need it to return:
"Initial Forms/General Forms/Background Check.pdf"
This will be a link I can use on the extranet. How do I get get-childitem to return just the sub-path?
My script is run from
//server/group/Creds/Documents
I can't find any examples similar to this. I'd like to avoid hard-coding the script location as well, in case it gets moved.
The easy way is to simply trim the unneeded path including trailing slash:
$filenameOut = "out.html"
#get current working dir
$cwd = Get-ScriptDirectory
#get files to display in lists
$temp = Join-Path $cwd "Initial Forms"
$temp = Join-Path $temp "General Forms"
$FullPath = Get-ChildItem -Path $temp
$InitialAppointmentGenArr = $FullPath | %{ $_.FullName.Replace($cwd + "\","")}
I suggest the following approch:
$relativeDirPath = Join-Path 'Initial Forms' 'General Forms'
Get-ChildItem -LiteralPath $PSScriptRoot/$relativeDirPath | ForEach-Object {
Join-Path $relativeDirPath $_.Name
}
Note that I've used $PSScriptRoot in lieu of $cwd, as it sound like the latter contains the directory in which your script is located, which automatic variable $PSScriptRoot directly reports.
Here's a generalized variation that also works with recursive use of Get-ChildItem:
$relativeDirPath = Join-Path 'Initial Forms' 'General Forms'
Get-ChildItem -LiteralPath $PSScriptRoot/$relativeDirPath | ForEach-Object {
$_.FullName.Substring($PSScriptRoot.Length + 1)
}
As an aside: In the cross-platform PowerShell (Core) 7+ edition, the underlying .NET Core framework's System.IO.Path type now has a .GetRelativePath() method, which is a convenient way to obtain a relative path from an absolute one, via a reference path:
# PowerShell (Core) 7+ only.
PS> [IO.Path]::GetRelativePath('/foo/bar', '/foo/bar/bam/baz.txt')
bam/baz.txt
Note:
Since .NET's working directory typically differs from PowerShell's, be sure to provide full input paths.
Also, be sure that the paths are file-system-native paths, not based on PowerShell-only drives.
Convert-Path can be used to determine full, file-system-native paths.

Extract the filename from a path

I want to extract filename from below path:
D:\Server\User\CUST\MEA\Data\In\Files\CORRECTED\CUST_MEAFile.csv
Now I wrote this code to get filename. This working fine as long as the folder level didn't change. But in case the folder level has been changed, this code need to rewrite. I looking a way to make it more flexible such as the code can always extract filename regardless of the folder level.
($outputFile).split('\')[9].substring(0)
If you are ok with including the extension this should do what you want.
$outputPath = "D:\Server\User\CUST\MEA\Data\In\Files\CORRECTED\CUST_MEAFile.csv"
$outputFile = Split-Path $outputPath -leaf
Use .net:
[System.IO.Path]::GetFileName("c:\foo.txt") returns foo.txt.
[System.IO.Path]::GetFileNameWithoutExtension("c:\foo.txt") returns foo
Using the BaseName in Get-ChildItem displays the name of the file and and using Name displays the file name with the extension.
$filepath = Get-ChildItem "E:\Test\Basic-English-Grammar-1.pdf"
$filepath.BaseName
Basic-English-Grammar-1
$filepath.Name
Basic-English-Grammar-1.pdf
Find a file using a wildcard and get the filename:
Resolve-Path "Package.1.0.191.*.zip" | Split-Path -leaf
$(Split-Path "D:\Server\User\CUST\MEA\Data\In\Files\CORRECTED\CUST_MEAFile.csv" -leaf)
Get-ChildItem "D:\Server\User\CUST\MEA\Data\In\Files\CORRECTED\CUST_MEAFile.csv"
|Select-Object -ExpandProperty Name
You could get the result you want like this.
$file = "D:\Server\User\CUST\MEA\Data\In\Files\CORRECTED\CUST_MEAFile.csv"
$a = $file.Split("\")
$index = $a.count - 1
$a.GetValue($index)
If you use "Get-ChildItem" to get the "fullname", you could also use "name" to just get the name of the file.
Just to complete the answer above that use .Net.
In this code the path is stored in the %1 argument (which is written in the registry under quote that are escaped: \"%1\" ). To retrieve it, we need the $arg (inbuilt arg). Don't forget the quote around $FilePath.
# Get the File path:
$FilePath = $args
Write-Host "FilePath: " $FilePath
# Get the complete file name:
$file_name_complete = [System.IO.Path]::GetFileName("$FilePath")
Write-Host "fileNameFull :" $file_name_complete
# Get File Name Without Extension:
$fileNameOnly = [System.IO.Path]::GetFileNameWithoutExtension("$FilePath")
Write-Host "fileNameOnly :" $fileNameOnly
# Get the Extension:
$fileExtensionOnly = [System.IO.Path]::GetExtension("$FilePath")
Write-Host "fileExtensionOnly :" $fileExtensionOnly
You can try this:
[System.IO.FileInfo]$path = "D:\Server\User\CUST\MEA\Data\In\Files\CORRECTED\CUST_MEAFile.csv"
# Returns name and extension
$path.Name
# Returns just name
$path.BaseName
$file = Get-Item -Path "c:/foo/foobar.txt"
$file.Name
Works with both relative and absolute paths

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. 😐

How to convert absolute path to relative path in PowerShell?

I'd like to convert a path to a relative path in a PowerShell script.
How do I do this using PowerShell?
For example:
Path to convert: c:\documents\mynicefiles\afile.txt
Reference path: c:\documents
Result: mynicefiles\afile.txt
And
Path to convert: c:\documents\myproject1\afile.txt
Reference path: c:\documents\myproject2
Result: ..\myproject1\afile.txt
I found something built in, Resolve-Path:
Resolve-Path -Relative
This returns the path relative to the current location. A simple usage:
$root = "C:\Users\Dave\"
$current = "C:\Users\Dave\Documents\"
$tmp = Get-Location
Set-Location $root
Resolve-Path -relative $current
Set-Location $tmp
Using the built-in System.IO.Path.GetRelativePath is simpler than the accepted answer:
[System.IO.Path]::GetRelativePath($relativeTo, $path)
There is a good answer here, but it changes your current directory (it reverts back), but if you really need to isolate that process, code example below can help. It does the same thing, just in a new PowerShell instance.
function Get-RelativePath($path, $relativeTo) {
$powershell = (Get-Process -PID $PID | Get-Item)
if ([string]::IsNullOrEmpty($powershell)) {
$powershell = "powershell.exe"
}
& $powershell -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -Command "& { Set-Location `"$relativeTo`"; Resolve-Path `"$path`" -Relative}"
}
It's really slow though, you should really use the other version unless you absolutely have to use this.
Sometimes I have multiple "roots" from which I want to generate relative file paths. I have found Resolve-Path -Relative unusable in this kind of situation. Changing a global setting, current location, in order to generate a relative file path seems error-prone and (if you're writing parallel code) possibly not thread-safe.
The following should work in early or recent versions of Powershell and Powershell Core, doesn't change your current directory, even temporarily, and is OS-independent and thread-safe.
It doesn't address the second example from OP (inserting .. as required.)
function Get-RelativePath {
param($path, $relativeTo)
# strip trailing slash
$relativeTo = Join-Path `
(Split-Path -Parent $relativeTo) `
(Split-Path -Leaf $relativeTo)
$relPath = Split-Path -Leaf $path
$path = Split-Path -Parent $path
do {
$leaf = Split-Path -Leaf $path
$relPath = Join-Path $leaf $relPath
$path = Split-Path -Parent $path
} until (($path -eq $relativeTo) -Or ($path.Length -eq 0))
$relPath
}
An example:
PS> $configRoot = 'C:\Users\P799634t\code\RMP\v2\AWD'
PS> $queryPath = 'C:\Users\P799634t\code\RMP\v2\AWD\config_queries\LOAD_UNQ_QUEUE_QUERY2.sql'
PS> Write-Host (Get-RelativePath $queryPath $configRoot)
config_queries\LOAD_UNQ_QUEUE_QUERY2.sql
It behaves reasonably when one file path is not a sub-path of the other:
PS> $root = 'C:\Users\P799634t\code\RMP\v2\AWD'
PS> $notRelated = 'E:\path\to\origami'
PS> Write-Host (Get-RelativePath $notRelated $root)
E:\path\to\origami
A quick and easy way would be :
$current -replace [regex]::Escape($root), '.'
Or if you want the relative path from your actual current location
$path -replace [regex]::Escape((pwd).Path), '.'
This assumes all your paths are valid.
Here is an alternative approach
$pathToConvert1 = "c:\documents\mynicefiles\afile.txt"
$referencePath1 = "c:\documents"
$result1 = $pathToConvert1.Substring($referencePath1.Length + 1)
#$result1: mynicefiles\afile.txt
And
$pathToConvert2 = "c:\documents\myproject1\afile.txt"
#$referencePath2 = "c:\documents\myproject2"
$result2 = "..\myproject" + [regex]::Replace($pathToConvert2 , ".*\d+", '')
#$result2: ..\myproject\afile.txt
Note: in the second case ref path wasn't used.

Removing path and extension from filename in PowerShell

I have a series of strings which are full paths to files. I'd like to save just the filename, without the file extension and the leading path. So from this:
c:\temp\myfile.txt
to
myfile
I'm not actually iterating through a directory, in which case something like PowerShell's basename property could be used, but rather I'm dealing with strings alone.
Way easier than I thought to address the issue of displaying the full path, directory, file name or file extension.
## Output:
$PSCommandPath ## C:\Users\user\Documents\code\ps\test.ps1
(Get-Item $PSCommandPath ).Extension ## .ps1
(Get-Item $PSCommandPath ).Basename ## test
(Get-Item $PSCommandPath ).Name ## test.ps1
(Get-Item $PSCommandPath ).DirectoryName ## C:\Users\user\Documents\code\ps
(Get-Item $PSCommandPath ).FullName ## C:\Users\user\Documents\code\ps\test.ps1
$ConfigINI = (Get-Item $PSCommandPath ).DirectoryName+"\"+(Get-Item $PSCommandPath ).BaseName+".ini"
$ConfigINI ## C:\Users\user\Documents\code\ps\test.ini
Other forms:
$scriptPath = split-path -parent $MyInvocation.MyCommand.Definition
split-path -parent $PSCommandPath
Split-Path $script:MyInvocation.MyCommand.Path
split-path -parent $MyInvocation.MyCommand.Definition
[io.path]::GetFileNameWithoutExtension($MyInvocation.MyCommand.Name)
There's a handy .NET method for that:
C:\PS> [io.path]::GetFileNameWithoutExtension("c:\temp\myfile.txt")
myfile
Inspired by an answer of #walid2mi:
(Get-Item 'c:\temp\myfile.txt').Basename
Please note: this only works if the given file really exists.
or
([io.fileinfo]"c:\temp\myfile.txt").basename
or
"c:\temp\myfile.txt".split('\.')[-2]
you can use basename property
PS II> ls *.ps1 | select basename
Starting with PowerShell 6, you get the filename without extension like so:
split-path c:\temp\myfile.txt -leafBase
#Keith,
here another option:
PS II> $f="C:\Downloads\ReSharperSetup.7.0.97.60.msi"
PS II> $f.split('\')[-1] -replace '\.\w+$'
PS II> $f.Substring(0,$f.LastIndexOf('.')).split('\')[-1]
Expanding on René Nyffenegger's answer, for those who do not have access to PowerShell version 6.x, we use Split Path, which doesn't test for file existence:
Split-Path "C:\Folder\SubFolder\myfile.txt" -Leaf
This returns "myfile.txt". If we know that the file name doesn't have periods in it, we can split the string and take the first part:
(Split-Path "C:\Folder\SubFolder\myfile.txt" -Leaf).Split('.') | Select -First 1
or
(Split-Path "C:\Folder\SubFolder\myfile.txt" -Leaf).Split('.')[0]
This returns "myfile".
If the file name might include periods, to be safe, we could use the following:
$FileName = Split-Path "C:\Folder\SubFolder\myfile.txt.config.txt" -Leaf
$Extension = $FileName.Split('.') | Select -Last 1
$FileNameWoExt = $FileName.Substring(0, $FileName.Length - $Extension.Length - 1)
This returns "myfile.txt.config".
Here I prefer to use Substring() instead of Replace() because the extension preceded by a period could also be part of the name, as in my example. By using Substring we return the filename without the extension as requested.
Given any arbitrary path string, various static methods on the System.IO.Path object give the following results.
strTestPath = C:\Users\DAG\Documents\Articles_2018\NTFS_File_Times_in_CMD\PathStringInfo.ps1
GetDirectoryName = C:\Users\DAG\Documents\Articles_2018\NTFS_File_Times_in_CMD
GetFileName = PathStringInfo.ps1
GetExtension = .ps1
GetFileNameWithoutExtension = PathStringInfo
Following is the code that generated the above output.
[console]::Writeline( "strTestPath = {0}{1}" ,
$strTestPath , [Environment]::NewLine );
[console]::Writeline( "GetDirectoryName = {0}" ,
[IO.Path]::GetDirectoryName( $strTestPath ) );
[console]::Writeline( "GetFileName = {0}" ,
[IO.Path]::GetFileName( $strTestPath ) );
[console]::Writeline( "GetExtension = {0}" ,
[IO.Path]::GetExtension( $strTestPath ) );
[console]::Writeline( "GetFileNameWithoutExtension = {0}" ,
[IO.Path]::GetFileNameWithoutExtension( $strTestPath ) );
Writing and testing the script that generated the above uncovered some quirks about how PowerShell differs from C#, C, C++, the Windows NT command scripting language, and just about everything else with which I have any experience.
Here is one without parentheses
[io.fileinfo] 'c:\temp\myfile.txt' | % basename
This script searches in a folder and sub folders and rename files by removing their extension
Get-ChildItem -Path "C:/" -Recurse -Filter *.wctc |
Foreach-Object {
rename-item $_.fullname -newname $_.basename
}
This can be done by splitting the string a couple of times.
#Path
$Link = "http://some.url/some/path/file.name"
#Split path on "/"
#Results of split will look like this :
# http:
#
# some.url
# some
# path
# file.name
$Split = $Link.Split("/")
#Count how many Split strings there are
#There are 6 strings that have been split in my example
$SplitCount = $Split.Count
#Select the last string
#Result of this selection :
# file.name
$FilenameWithExtension = $Split[$SplitCount -1]
#Split filename on "."
#Result of this split :
# file
# name
$FilenameWithExtensionSplit = $FilenameWithExtension.Split(".")
#Select the first half
#Result of this selection :
# file
$FilenameWithoutExtension = $FilenameWithExtensionSplit[0]
#The filename without extension is in this variable now
# file
$FilenameWithoutExtension
Here is the code without comments :
$Link = "http://some.url/some/path/file.name"
$Split = $Link.Split("/")
$SplitCount = $Split.Count
$FilenameWithExtension = $Split[$SplitCount -1]
$FilenameWithExtensionSplit = $FilenameWithExtension.Split(".")
$FilenameWithoutExtension = $FilenameWithExtensionSplit[0]
$FilenameWithoutExtension
The command below will store in a variable all the file in your folder, matchting the extension ".txt":
$allfiles=Get-ChildItem -Path C:\temp\*" -Include *.txt
foreach ($file in $allfiles) {
Write-Host $file
Write-Host $file.name
Write-Host $file.basename
}
$file gives the file with path, name and extension: c:\temp\myfile.txt
$file.name gives file name & extension: myfile.txt
$file.basename gives only filename: myfile
Here are a couple PowerShell 5.1 one-liner options that put the path at the start of the line.
'c:\temp\myfile.txt' |%{[io.fileinfo]$_ |% basename}
OR
"c:\temp\myfile.txt" | Split-Path -Leaf | %{$_ -replace '\.\w+$'}