Automated profile update Powershell - drive not found - powershell

I am struggling with my script - for some reason, the PSDrive that my script creates is not accessible for Resolve-Path.
In general, in the script there is "Start-RDP" function which starts RDP with preloaded credentials (autologon), and then checks if the Powershell profile on the target host is up to date (by comparing the filehashes). However, in order for the script to access the remote filesystem I need to mount it as PSDrive.
Here is the script that is offending. All the variables are set properly during that time, above in the script.
New-PSDrive -name "$computername" -Root "\\$computername\c$" -Credential $CurrentCred -PSProvider FileSystem | out-null
Start-Sleep -Seconds 10
while (!(Test-Path -Path ${Computername}:\$Userpath\$Documents\)) { Write-host "UserDir not created yet!" ; start-sleep -Seconds 5 }
if (Test-Path -Path ${Computername}:\$Userpath\$Documents\WindowsPowerShell) {
$ProfileHash = Get-FileHash $Profile.CurrentUserAllHosts
if (!(Test-Path "${computername}:\$Userpath\$Documents\WindowsPowerShell\profile.ps1")) { Copy-Item -Force -Path "$env:userprofile\WindowsPowershell\profile.ps1" -Destination "${computername}:\$Userpath\$Documents\WindowsPowerShell\" }
$RemoteProfileHash = Get-FileHash "${computername}:\$Userpath\$Documents\WindowsPowerShell\profile.ps1"
if ($ProfileHash -ne $RemoteProfileHash) { Copy-Item -Force -Path "$env:userprofile\$Documents\WindowsPowershell\profile.ps1" -Destination "${computername}:\$userpath\$Documents\WindowsPowerShell\" }
}
The error I am getting is at second Test-Path (where I check if WindowsPowerShell directory exists).
Resolve-Path : Cannot find drive. A drive with the name 'server01' does not exist.
At C:\windows\system32\windowspowershell\v1.0\Modules\Microsoft.PowerShell.Utility\Microsoft.PowerShell.Utility.psm1:35 char:32
+ $pathsToProcess += Resolve-Path $Path | Foreach-Object ProviderPath
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (server01:String) [Resolve-Path], DriveNotFoundException
+ FullyQualifiedErrorId : DriveNotFound,Microsoft.PowerShell.Commands.ResolvePathCommand
I am unable to trace down the specific reason this error occurs. The drive is there (I checked using PSBreakpoint)
I'm kind of stuck at this for some time now, do you have any ideas on that one?

I see what you did there.
The problem is that you are using the variable $Profile.CurrentUserAllHosts which powershell is trying to resolve as a complete variable name. $Profile is a string, which has no property called CurrentUserAllHosts. To fix, use the following:
$ProfileHash = Get-FileHash "${Profile}.CurrentUserAllHosts"
After some more investigation, I found this snippet on a blog
commands like Resolve-Path and $PSCmdlet.GetUnresolvedProviderPathFromPSPath() don’t normalize UNC paths properly, even when the FileSystem provider handles them.
Which then links to the Get-NormalizedFileSystemPath script on technet.
Since Get-FileHash is a system provided method, you'll want to Get-NormalizedFileSystemPath before passing it to Get-FileHash
And for posterity sake, here's the script:
function Get-NormalizedFileSystemPath
{
<#
.Synopsis
Normalizes file system paths.
.DESCRIPTION
Normalizes file system paths. This is similar to what the Resolve-Path cmdlet does, except Get-NormalizedFileSystemPath also properly handles UNC paths and converts 8.3 short names to long paths.
.PARAMETER Path
The path or paths to be normalized.
.PARAMETER IncludeProviderPrefix
If this switch is passed, normalized paths will be prefixed with 'FileSystem::'. This allows them to be reliably passed to cmdlets such as Get-Content, Get-Item, etc, regardless of Powershell's current location.
.EXAMPLE
Get-NormalizedFileSystemPath -Path '\\server\share\.\SomeFolder\..\SomeOtherFolder\File.txt'
Returns '\\server\share\SomeOtherFolder\File.txt'
.EXAMPLE
'\\server\c$\.\SomeFolder\..\PROGRA~1' | Get-NormalizedFileSystemPath -IncludeProviderPrefix
Assuming you can access the c$ share on \\server, and PROGRA~1 is the short name for "Program Files" (which is common), returns:
'FileSystem::\\server\c$\Program Files'
.INPUTS
String
.OUTPUTS
String
.NOTES
Paths passed to this command cannot contain wildcards; these will be treated as invalid characters by the .NET Framework classes which do the work of validating and normalizing the path.
.LINK
Resolve-Path
#>
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[Alias('PSPath', 'FullName')]
[string[]]
$Path,
[switch]
$IncludeProviderPrefix
)
process
{
foreach ($_path in $Path)
{
$_resolved = $_path
if ($_resolved -match '^([^:]+)::')
{
$providerName = $matches[1]
if ($providerName -ne 'FileSystem')
{
Write-Error "Only FileSystem paths may be passed to Get-NormalizedFileSystemPath. Value '$_path' is for provider '$providerName'."
continue
}
$_resolved = $_resolved.Substring($matches[0].Length)
}
if (-not [System.IO.Path]::IsPathRooted($_resolved))
{
$_resolved = Join-Path -Path $PSCmdlet.SessionState.Path.CurrentFileSystemLocation -ChildPath $_resolved
}
try
{
$dirInfo = New-Object System.IO.DirectoryInfo($_resolved)
}
catch
{
$exception = $_.Exception
while ($null -ne $exception.InnerException)
{
$exception = $exception.InnerException
}
Write-Error "Value '$_path' could not be parsed as a FileSystem path: $($exception.Message)"
continue
}
$_resolved = $dirInfo.FullName
if ($IncludeProviderPrefix)
{
$_resolved = "FileSystem::$_resolved"
}
Write-Output $_resolved
}
} # process
} # function Get-NormalizedFileSystemPath

Related

I have a file organiser powershell script which runs without any error but it doesnt perform the moving operation

It is a file organiser script I wrote for myself. For a specific purpose of mine. Whenever I try to run it It runs and closes off. But the move operation is not happening. The below comments may help you understand what the code is doing.Please help me on what am i doing wrong here. I am extremely new to Powershell Scripting.
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
# Global variable declarations
$global:pathsFromConfig = Get-Content -Path $PSScriptRoot"\MoverPaths.txt"
$global:categoriesFromConfig = Get-Content -Path $PSScriptRoot"\MoverCategories.txt"
$global:categryHash = #{}
# Method call to read configs, create dirs, & move files
readCreateAndMoveFiles
# Method definition
function readCreateAndMoveFiles{
# Reads categories config.txt and splits them line by line
# Adds each line as a key value pair to a hashtable
foreach($category in $categoriesFromConfig)
{
$temp = $category -split ":"
$categryHash.add($temp[0].trim().toString(),($temp[1]).trim().toString())
}
# For each category in the hash table, calls create directory method, and then moves the files based on current category
foreach($hashItem in $categryHash.GetEnumerator()){
# Creates a directory with the Hash Key
Foreach($pathToMonitor in $pathsFromConfig){
$categoryFullPath = $pathToMonitor+$hashItem.Name
createDirectory($categoryFullPath)
# Moves files into that directory
Set-Location -Path $pathToMonitor
$extentions = $hashItem.Value
Get-Item $extentions | Move-Item -Destination $categoryFullPath
$categoryFullPath = ""
}
}
}
# Method Definition
function createDirectory ($categoryName)
{
if(Test-Path -Path $categoryName)
{
# Directory already Exists!
}
else
{
# Creates Directory
md $categoryName
}
}
The config files are hereby:
MoverCategories.txt
Images:*.jpg,*.jpeg,*.png,*.tiff,*.raw,*.heic,*.gif,*.svg,*.eps,*.ico
Documents:*.txt,*.pdf,*.doc,*.docx,*.xls,*.xlsx,*.ppt,*.pptx,*.html,*.xls,*.csv,*.rtx
MoverPaths.txt
D:\Downloads\
Found a way to do this. Thanks for all of your input. Now the script moves files. Instead of sending all extentions in a single shot, i made it into an array and sent it one by one. Now it works fine. If you guys could help me reduce the time of execution that would be great.But the code works now I am happy.
foreach($hashItem in $categryHash.GetEnumerator()){
# Creates a directory with the Hash Key
Foreach($pathToMonitor in $pathsFromConfig){
$categoryFullPath = $pathToMonitor+$hashItem.Name
createDirectory($categoryFullPath)
# Moves files into that directory
[String[]]$extentions = #()
$extentions = $hashItem.Value -split ','
foreach($string in $extentions)
{
Get-Item $pathToMonitor\* -Include $string | Move-Item -Destination $categoryFullPath
}
}
}
Try this
#specify path(s)
$path = "$env:USERPROFILE\Downloads"
## this is make an array of the extensions in the foloder
$extensions = Get-ChildItem -Path $path | Select-Object -Unique -Property #{label = 'ext'
expression = { $_.Extension.substring(1) }
}
## this function will
function New-FoldersByName {
param
(
[Parameter(Mandatory = $true, ValueFromPipeline = $true, HelpMessage = 'Data to process')]
$InputObject
)
process {
Set-Location $path
if (!(Test-Path -PathType Container $InputObject )) {
New-Item -ItemType directory -Name $InputObject.ext -WhatIf
Write-Host -Message "A folder named $($InputObject.ext) does not exist. Creating..."
}
else {
Write-Host -Message "A folder named $($InputObject.ext) already exists. Skipping..."
}
}
}
##this is a reuseable function to moves items in a folder into a subfolder named after the files extension
## if extension is .exe the file with be moved to ./EXE/filename.exe
function Move-ItemsByName {
param
(
[Parameter(Mandatory = $true, ValueFromPipeline = $true, HelpMessage = 'Data to process')]
$InputObject
)
process {
Set-Location -Path $path
Move-Item -Path ('*.{0}' -f $InputObject.ext) -Destination ('{0}' -f $InputObject.ext) -WhatIf
}
}
$extensions | New-FoldersByName
$extensions | Move-ItemsByName

powershell Get-Content and .Net Open() handing path differently [duplicate]

This question already has answers here:
How does PowerShell treat "." in paths?
(3 answers)
Closed 1 year ago.
Get-Content appears to use the current working directory location to resolve realative paths. However, the .Net System.Io.File Open() method does not. What is the PowerShell-centric way to resolve a relative path for .Net?
PS C:\src\t> type .\ReadWays.ps1
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[String]$Path
)
Write-Host "Path is $Path"
Get-Content -Path $Path | Out-Null
if ([System.IO.StreamReader]$sr = [System.IO.File]::Open($Path, [System.IO.FileMode]::Open)) { $sr.Close() }
PS C:\src\t> .\ReadWays.ps1 -Path '.\t.txt'
Path is .\t.txt
MethodInvocationException: C:\src\t\ReadWays.ps1:8
Line |
8 | if ([System.IO.StreamReader]$sr = [System.IO.File]::Open($Path, [Syst …
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| Exception calling "Open" with "2" argument(s): "Could not find file 'C:\Program Files\PowerShell\7\t.txt'."
PS C:\src\t> $PSVersionTable.PSVersion.ToString()
7.2.0
You can add a test to see if the path is relative and if so, convert it to absolute like:
if (![System.IO.Path]::IsPathRooted($Path) -or $Path -match '^\\[^\\]+') {
$path = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($pwd, $Path))
}
I added $Path -match '^\\[^\\]+' to also convert relative paths starting with a backslash like \ReadWays.ps1 meaning the path starts at the root directory. UNC paths that start with two backslashes are regarded as absolute.
The following works fine for me and is compatible with Windows
and Linux. This is using Convert-Path to resolve the relative paths. I was previously using Resolve-Path which is incorrect, only the former resolves to file-system-native paths, thanks mklement0 for pointing it out
param(
[ValidateScript({
if(Test-Path $_ -PathType Leaf)
{
return $true
}
throw 'Invalid File Path'
})]
[string]$Path
)
if(-not $Path.StartsWith('\\'))
{
[string]$Path = Convert-Path $Path
}
$reader = [System.IO.StreamReader]::new(
[System.IO.File]::Open(
$Path, [System.IO.FileMode]::Open
)
)
$reader.BaseStream
$reader.Close()
Last Edit
The following should be able to handle:
UNC Paths
Work on Windows and Linux
Be efficient
Handle Relative Paths
Starting from the base that $Path is valid thanks to the ValidateScript attribute, we only need to determine if the path we are dealing with is UNC, Relative or Absolute.
UNC paths must always be fully qualified. They can include relative directory segments (. and ..), but these must be part of a fully qualified path. You can use relative paths only by mapping a UNC path to a drive letter.
We can assume a UNC path must always start with \\, so this condition should suffice to determine if $Path will be manipulated or not:
if(-not $Path.StartsWith('\\'))
Lastly, in the begin block, updating the environment's current directory each time our script or function runs with:
[Environment]::CurrentDirectory = $pwd.ProviderPath
By doing so, ([System.IO.FileInfo]$Path).FullName should give us the absolute path of our parameter, be it UNC, Relative or Absolute.
param(
[ValidateScript({
if(Test-Path $_ -PathType Leaf) {
return $true
}
throw 'Invalid File Path'
})] [string]$Path
)
begin
{
[Environment]::CurrentDirectory = $pwd.ProviderPath
}
process
{
if(-not $Path.StartsWith('\\'))
{
$Path = ([System.IO.FileInfo]$Path).FullName
}
try
{
$reader = [System.IO.StreamReader]::new(
[System.IO.File]::Open(
$Path, [System.IO.FileMode]::Open
)
)
$reader.BaseStream
}
catch
{
$_.Exception.Message
}
finally
{
$reader.Close()
$reader.Dispose()
}
}
This is a common question. Somehow .net and powershell don't agree on the current directory.
[System.IO.File]::Open("$pwd\$Path", [System.IO.FileMode]::Open)

How do I modify this script to take parameters?

I have a power shell script that combines power points. The issue is it only works on power points in the current directory (the directory the script is in) and saves the combined power point to documents. How do I change the script to work from any directory that is given as a parameter. I run the power shell script like this ./Merge-Presentation -Source $Presentations -Destination $save -Verbose -Open;. Where $Presentations is the path of the individual power point and $save is the path where the combined power point is saved. Here is the script.
#region function definitions
#Function for releasing a COM object
Function Remove-Ref
{
param
(
[Object]
$ref
)
$null = Remove-Variable -Name $ref -ErrorAction SilentlyContinue
while ([System.Runtime.InteropServices.Marshal]::ReleaseComObject([System.__ComObject]$ref) -gt 0)
{
}
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
}
#Main function for merging PowerPoint presentations
Function Merge-PowerPointPresentation
{
<#
.SYNOPSIS
Merge multiple PowerPoint presentation files to one file
.DESCRIPTION
Merge multiple PowerPoint presentation files to one file
.PARAMETER Source
The PowerPoint presentation files to merge specified by its full name
.PARAMETER Destination
The target PowerPoint presentation file specified by its full name
.PARAMETER Open
A switch to specify if we keep the PowerPoint application opened after the processing
.EXAMPLE
$Get-ChildItem -Path $CurrentDir -filter *.pptx | Sort-Object -Property Name | Merge-PowerPointPresentation -Verbose -Open
Will merge all the PowerPoint files into the current directory into one single Powerpoint file by using a timestamped filename (ie. yyyyMMddTHHmmss.pptx like 20170126T091011.pptx)
The output will be verbose
The PowerPoint application won't be left after the processing
.EXAMPLE
$Presentations = "$CurrentDir\0.pptx","$CurrentDir\1.pptx","$CurrentDir\2.pptx","$CurrentDir\3.pptx","$CurrentDir\4.pptx","$CurrentDir\5.pptx","$CurrentDir\6.pptx","$CurrentDir\7.pptx","$CurrentDir\8.pptx","$CurrentDir\9.pptx"
Merge-PowerPointPresentation -Source $Presentations -Destination C:\Temp\MergedPresentation.pptx
Will merge all the specified PowerPoint files into into the C:\Temp\MergedPresentation.pptx Powerpoint file
#>
[CmdletBinding()]
Param(
#The collection of the powerpoint files to merge
[Parameter(Mandatory = $True,ValueFromPipeline = $True,ValueFromPipelineByPropertyName = $True)]
[ValidateScript({
(Test-Path -Path $_ -PathType Leaf) -and ($_ -match "\.ppt(x{0,1})$")
})]
[alias('FilePath', 'Path', 'FullName')]
[string[]]$Source,
#The path of the generated powerpoint file
[Parameter(Mandatory = $False)]
[ValidateNotNullOrEmpty()]
[alias('OutputFile')]
[string]$Destination = $(Join-Path -Path $([Environment]::GetFolderPath('MyDocuments')) -ChildPath $('{0:yyyyMMddTHHmmss}' -f (Get-Date))),
#To keep open the generated Powerpoint presentation
[parameter(Mandatory = $False)]
[switch]$Open
)
begin
{
#Opening the PowerPoint application once
Add-Type -AssemblyName Microsoft.Office.Interop.PowerPoint
$Powerpoint = New-Object -ComObject Powerpoint.Application
#Creating a new PowerPoint presentation
$NewPresentation = $Powerpoint.Presentations.Add($True)
# Adding an empty slide : mandatory
$null = $NewPresentation.Slides.Add(1, [Microsoft.Office.Interop.PowerPoint.PpSlideLayout]::ppLayoutBlank)
$SlidesNb = 0
}
process
{
#For all files passed as argument outside a pipeline context
foreach ($CurrentSource in $Source)
{
#Getting the base name of the processed presentation
$CurrentPresentationName = (Get-Item -Path $CurrentSource).BaseName
#Inserting the slide of the current presentationt o the new one
$InsertedSlidesNb = $NewPresentation.Slides.InsertFromfile($CurrentSource, $SlidesNb)
#Applying the original template
$NewPresentation.Slides.Range(($SlidesNb+1)..($SlidesNb+$InsertedSlidesNb)).ApplyTemplate($CurrentSource)
#Adding a new section for the inserted context with the name of the processed presentation
Write-Verbose -Message "Adding the section $CurrentPresentationName before Slide $($SlidesNb+1)..."
$null = $NewPresentation.SectionProperties.AddBeforeSlide($SlidesNb+1, $CurrentPresentationName)
Write-Verbose -Message "Processed file $CurrentSource by inserting $InsertedSlidesNb slides ($($SlidesNb+1) ==> $($SlidesNb+$InsertedSlidesNb)) ..."
$SlidesNb += $InsertedSlidesNb
}
}
end
{
#Deleting the useless empty slide (added at the beginning)
$NewPresentation.Slides.Range($SlidesNb+1).Delete()
#Saving the final file
$NewPresentation.SaveAs($Destination)
Write-Host -Object "The new presentation was saved in $($NewPresentation.FullName) ($SlidesNb slides)"
#If the -Open switch is specified we keep the PowerPoint application opened
if (!$Open)
{
$NewPresentation.Close()
#$Powerpoint.Quit() | Out-Null
Write-Verbose -Message 'Releasing PowerPoint ...'
Remove-Ref -ref ($NewPresentation)
Remove-Ref -ref ($Powerpoint)
}
}
}
#endregion
Clear-Host
#Getting the current directory (where this script file resides)
$CurrentDir = Split-Path -Path $MyInvocation.MyCommand.Path
#Loading the PowerPoint assembly
#Example 1 : Processing all the PowerPoint presentation in current directory in the alphabetical order
Get-ChildItem -Path $CurrentDir -Filter *.pptx |
Sort-Object -Property Name |
Merge-PowerPointPresentation -Verbose -Open
#Example 2 : Processing a list of some PowerPoint presentations specified by their absolute path
$Presentations = "$CurrentDir\0.pptx", "$CurrentDir\1.pptx", "$CurrentDir\2.pptx", "$CurrentDir\3.pptx", "$CurrentDir\4.pptx", "$CurrentDir\5.pptx", "$CurrentDir\6.pptx", "$CurrentDir\7.pptx", "$CurrentDir\8.pptx", "$CurrentDir\9.pptx"
Merge-PowerPointPresentation -Source $Presentations -Destination $CurrentDir\all.pptx -Verbose
The expected result is to load the power points from the directory specified as parameter and save the combined power point in the directory specified as a parameter.
You can just add a parameter declaration at the top, something like:
Param(
[parameter(Mandatory = $false, Position = 1)]
[ValidateScript( {Test-Path $_} )]
[String]$SourcePath = ( Split-Path $MyInvocation.MyCommand.Path ),
[parameter(Mandatory = $false, Position = 2)]
[ValidateScript( {Test-Path $_} )]
[String]$DestinationPath
) #End Parameter Block...
I used Path as the parameter because it's quite common and $CurrentDirector wouldn't make sense here. I defaulted the parameter to the same as you had it. In the script body you'll want to replace $CurrentPath with $Path.
Get-ChildItem -Path $SourcePath -Filter *.pptx |
Sort-Object -Property Name |
Merge-PowerPointPresentation -Verbose -Open -Destination $DestinationPath
Note: The addition of -DestinationPath in the above call. That should override the default value in the Merge-PowerPointPresentation function.
If you don't use the parameters the script should behave same as before. If you do use it It will operate against whatever paths you gave it.
Obviously not tested, but let me know if that helps. Thanks.

Proxy function in PowerShell not accepting pipeline input

I've created a proxy function for Remove-Item, which deletes to the recycle bin instead of permanently (using the proxy so that I can seamlessly replace the rm alias, without breaking 3rd party scripts).
However, it doesn't work when a file is piped into the function. The heart of the proxy function is this:
if ($PSBoundParameters['DeletePermanently'] -or $PSBoundParameters['LiteralPath'] -or $PSBoundParameters['Filter'] -or $PSBoundParameters['Include'] -or $PSBoundParameters['Exclude'] -or $PSBoundParameters['Recurse'] -or $PSBoundParameters['Force'] -or $PSBoundParameters['Credential']) {
if ($PSBoundParameters['DeletePermanently']) { $PSBoundParameters.Remove('DeletePermanently') | Out-Null }
$scriptCmd = {& $wrappedCmd #PSBoundParameters }
} else {
$scriptCmd = {& Recycle-Item -Path $PSBoundParameters['Path'] }
}
So, my custom Recycle-Item function is only called if Path is the only parameter. So, something like Get-ChildItem .\temp\ | rm -DeletePermanently works just fine, but Get-ChildItem .\temp\ | rm has an error because the Path passed to Recycle-Item is $null.
I've tried passing $Path instead of $PSBoundParameters['Path'] and tried splatting #PSBoundParameters like the call to $wrappedCmd above, but none of it appears to do much good. I've copied the params from this function to Recycle-Item, to ensure that it is expecting input from the pipeline, but that doesn't seem to help either. Some of those changes appear to pass along the file name, but not the full path, so I don't know if there's some magic inside Remove-Item that I need to replicate to handle a file object from the pipeline.
Recycle-Item is just a basic function:
function Recycle-Item($Path) {
$item = Get-Item $Path
$directoryPath = Split-Path $item -Parent
$shell = new-object -comobject "Shell.Application"
$shellFolder = $shell.Namespace($directoryPath)
$shellItem = $shellFolder.ParseName($item.Name)
$shellItem.InvokeVerb("delete")
}
As mentioned in the comments, the provider cmdlets usually bind on LiteralPath when you pipe objects between them. This way allows Path to support wildcard globbing without the chance of passing ambiguous item paths between cmdlets.
Remove-Item has only two parameter sets, and they are named after their mandatory parameters, Path and LiteralPath
To solve your problem, simply check for all defined parameters that are not one of these two, then pass the appropriate value to Remove-Item based on the $PSCmdlet.ParameterSetName value:
if(#($PSBoundParameters.Keys |Where-Object {#('DeletePermanently','Filter','Include','Exclude','Recurse','Force','Credential') -contains $_}).Count -ge 1){
# a parameter other than the Path/LiteralPath or the common parameters was specified, default to Remove-Item
if ($PSBoundParameters['DeletePermanently']) {
$PSBoundParameters.Remove('DeletePermanently') | Out-Null
}
$scriptCmd = {& $wrappedCmd #PSBoundParameters }
} else {
# apart from common parameters, only Path/LiteralPath was specified, go for Recycle-Item
$scriptCmd = {& Recycle-Item -Path $PSBoundParameters[$PSCmdlet.ParameterSetName] }
}

In PowerShell, how can I test if a user-supplied string represents a full absolute path? [duplicate]

I'm trying to process a list of files that may or may not be up to date and may or may not yet exist. In doing so, I need to resolve the full path of an item, even though the item may be specified with relative paths. However, Resolve-Path prints an error when used with a non-existant file.
For example, What's the simplest, cleanest way to resolve ".\newdir\newfile.txt" to "C:\Current\Working\Directory\newdir\newfile.txt" in Powershell?
Note that System.IO.Path's static method use with the process's working directory - which isn't the powershell current location.
You want:
c:\path\exists\> $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath(".\nonexist\foo.txt")
returns:
c:\path\exists\nonexists\foo.txt
This has the advantage of working with PSPaths, not native filesystem paths. A PSPath may not map 1-1 to a filesystem path, for example if you mount a psdrive with a multi-letter drive name.
What's a pspath?
ps c:\> new-psdrive temp filesystem c:\temp
...
ps c:\> cd temp:
ps temp:\>
temp:\ is a drive-qualified pspath that maps to a win32 (native) path of c:\temp.
-Oisin
When Resolve-Path fails due to the file not existing, the fully resolved path is accessible from the thrown error object.
You can use a function like the following to fix Resolve-Path and make it work like you expect.
function Force-Resolve-Path {
<#
.SYNOPSIS
Calls Resolve-Path but works for files that don't exist.
.REMARKS
From http://devhawk.net/blog/2010/1/22/fixing-powershells-busted-resolve-path-cmdlet
#>
param (
[string] $FileName
)
$FileName = Resolve-Path $FileName -ErrorAction SilentlyContinue `
-ErrorVariable _frperror
if (-not($FileName)) {
$FileName = $_frperror[0].TargetObject
}
return $FileName
}
I think you're on the right path. Just use [Environment]::CurrentDirectory to set .NET's notion of the process's current dir e.g.:
[Environment]::CurrentDirectory = $pwd
[IO.Path]::GetFullPath(".\xyz")
Join-Path (Resolve-Path .) newdir\newfile.txt
This has the advantage of not having to set the CLR Environment's current directory:
[IO.Path]::Combine($pwd,"non\existing\path")
NOTE
This is not functionally equivalent to x0n's answer. System.IO.Path.Combine only combines string path segments. Its main utility is keeping the developer from having to worry about slashes. GetUnresolvedProviderPathFromPSPath will traverse the input path relative to the present working directory, according to the .'s and ..'s.
I've found that the following works well enough.
$workingDirectory = Convert-Path (Resolve-Path -path ".")
$newFile = "newDir\newFile.txt"
Do-Something-With "$workingDirectory\$newFile"
Convert-Path can be used to get the path as a string, although this is not always the case. See this entry on COnvert-Path for more details.
function Get-FullName()
{
[CmdletBinding()]
Param(
[Parameter(ValueFromPipeline = $True)] [object[]] $Path
)
Begin{
$Path = #($Path);
}
Process{
foreach($p in $Path)
{
if($p -eq $null -or $p -match '^\s*$'){$p = [IO.Path]::GetFullPath(".");}
elseif($p -is [System.IO.FileInfo]){$p = $p.FullName;}
else{$p = [IO.Path]::GetFullPath($p);}
$p;
}
}
}
I ended up with this code in my case. I needed to create a file later in the the script, so this code presumes you have write access to the target folder.
$File = ".\newdir\newfile.txt"
If (Test-Path $File) {
$Resolved = (Resolve-Path $File).Path
} else {
New-Item $File -ItemType File | Out-Null
$Resolved = (Resolve-Path $File).Path
Remove-Item $File
}
I also enclosed New-Item in try..catch block, but that goes out of this question.
I had a similar issue where I needed to find the folder 3 levels up from a folder that does not exist yet to determine the name for a new folder I wanted to create... It's complicated. Anyway, this is what I ended up doing:
($path -split "\\" | select -SkipLast 3) -join "\\"
You can just set the -errorAction to "SilentlyContinue" and use Resolve-Path
5 > (Resolve-Path .\AllFilerData.xml -ea 0).Path
C:\Users\Andy.Schneider\Documents\WindowsPowerShell\Scripts\AllFilerData.xml
6 > (Resolve-Path .\DoesNotExist -ea 0).Path
7 >
There is an accepted answer here, but it is quite lengthy and there is a simpler alternative available.
In any recent version of Powershell, you can use Test-Path -IsValid -Path 'C:\Probably Fake\Path.txt'
This simply verifies that there are no illegal characters in the path and that the path could be used to store a file. If the target doesn't exist, Test-Path won't care in this instance -- it's only being asked to test if the provided path is potentially valid.
Both most popular answers don't work correctly on paths on not existing drives.
function NormalizePath($filename)
{
$filename += '\'
$filename = $filename -replace '\\(\.?\\)+','\'
while ($filename -match '\\([^\\.]|\.[^\\.]|\.\.[^\\])[^\\]*\\\.\.\\') {
$filename = $filename -replace '\\([^\\.]|\.[^\\.]|\.\.[^\\])[^\\]*\\\.\.\\','\'
}
return $filename.TrimEnd('\')
}
Check if the file exists before resolving:
if(Test-Path .\newdir\newfile.txt) { (Resolve-Path .\newdir\newfile.txt).Path }