Using param aliases in PowerShell - powershell

I've created a renaming script that includes aliases for variables that I would like to define as parameters when I call the script. If I define the variables in the script the traditional way ($variable = 'defined') the script renames the appropriate file correctly. I think I am misunderstanding the param portion.
Here's the script:
#retrieve the name from the text file
$name = Get-Content "C:\name.txt"
#identify the .jpg that needs to be renamed based on naming (Get-Date -Format "yyyy-MM-dd HH-mm-ss")
$fileName = Get-Date -Format "yyyy-MM-dd"
param (
[Parameter(<#mandatory=$true#>)]
[alias("v")]
[string] $variant,
[Parameter(<#mandatory=$true#>)]
[alias("pc")]
[string] $processClass
)
#identify new name set up and identify the file to address (oldname) then rename it
$newname = "D:\Output\$name" + "." + $variant + ".PC_" + $processClass + ".jpg"
$oldName = gci "D:\Output\" -Filter *.jpg | Where-Object {$_.name -like "*$fileName*"}
rename-item $oldName.FullName $newname -Force
Then I will call the script with the parameters applied like this:
powershell.exe -File C:\Rename.ps1 -v "XXX" -pc "YYY"
However at this point, the $oldName file is only partially renamed, missing the $variant and $processClass portion and I get a PowerShell error:
param : The term 'param' is not recognized as the name of a cmdlet,
function, script file, or operable program. Check the spelling of the
name, or if a path was included, verify that the path is correct and
try again.
It seems im not properly defining the parameters or I have just misunderstood how this part works, I've not tried this before.
Thanks for any suggestions

your problem is the placement of code before the Param() block. [grin] simply move all that pre-Param() code to after the Param() block and things will work.
you may also want to consider adding [CmdletBinding()] just before the Param() to make it a full "advanced function".

Related

Powershell - Copying files with space and variables in the path

I have the script below, I am trying to use copy-item to copy two files. Unfortunately the file path include spaces in them, and I need to use variables to capture the correct files. I have used Test-Path to try and qualify the path, which seem to work, but in the script it is all failures.
$Today = Get-Date -Format "yyyyMMdd"
$Yesterday = (Get-Date).AddDays(-1).ToString('yyyyMMdd')
$folderdate = $Yesterday
$filedate = (Get-Date).AddDays(-1).ToString('dd.MM.yy')
Copy-Item -Path \\posa1251\d$\File_Transfer\NBS\$Today\Daily MI $Yesterday'.xlsx' -Destination \\wbbsmd.co.uk\corpdata\Corp\Group\NBS_WebSave_Reports\$folderdate\
Copy-Item -Path \\posa1251\d$\File_Transfer\NBS\$Yesterday\$filedate' - West Brom MI.xls' -Destination \\wbbsmd.co.uk\corpdata\Corp\Group\NBS_WebSave_Reports\$folderdate\
The file paths without the variables are below:
\\posa1251\d$\File_Transfer\NBS\20230201\Daily MI 20230131.xlsx
\\posa1251\d$\File_Transfer\NBS\20230131\31.01.23 - West Brom MI.xls
\\wbbsmd.co.uk\corpdata\Corp\Group\NBS_WebSave_Reports\20230131\
Any suggestions would be gratefully received.
Essentially, your paths should be wrapped in a expandable string "..." otherwise each value between the spaces are interpreted and bound to other parameters, considering there are available positional parameters available. See about Parameters for more details.
Take the following simple function to understand what's happening:
function Test {
param(
[Parameter(Mandatory)]
[string[]] $Path,
[Parameter(Mandatory)]
[string] $Destination,
[Parameter(ValueFromRemainingArguments)]
[object[]] $RemainingArgs
)
$PSBoundParameters
}
If we try the paths without quotes:
Test -Path \\posa1251\d$\File_Transfer\NBS\$Today\Daily MI $Yesterday'.xlsx' -Destination \\wbbsmd.co.uk\corpdata\Corp\Group\NBS_WebSave_Reports\$folderdate\
We can see that MI and 20230201.xlsx are being bound to other, remaining in this case, parameters:
Key Value
--- -----
Path {\\posa1251\d$\File_Transfer\NBS\20230202\Daily}
Destination \\wbbsmd.co.uk\corpdata\Corp\Group\NBS_WebSave_Reports\\
RemainingArgs {MI, 20230201.xlsx}
Instead, if we use quotes:
Test -Path "\\posa1251\d$\File_Transfer\NBS\$Today\Daily MI $Yesterday.xlsx" -Destination "\\wbbsmd.co.uk\corpdata\Corp\Group\NBS_WebSave_Reports\$folderdate\"
There shouldn't be any problem:
Key Value
--- -----
Path {\\posa1251\d$\File_Transfer\NBS\20230202\Daily MI 20230201.xlsx}
Destination \\wbbsmd.co.uk\corpdata\Corp\Group\NBS_WebSave_Reports\\

How can I tell if a specified folder is in my PATH using PowerShell?

How can I tell if a specified folder is in my PATH using PowerShell?
A function like this would be great:
function FolderIsInPATH($Path_to_directory) {
# If the directory is in PATH, return true, otherwise false
}
Going off this question, you don't need a function for this but can retrieve this with $Env:Path:
$Env:Path -split ";" -contains $directory
The -contains operator is case-insensitive which is a bonus. It could be useful placing this in a function to ensure trailing slashes are trimmed, but that's uncommon:
function inPath($directory) {
return ($Env:Path -split ';').TrimEnd('\') -contains $directory.TrimEnd('\')
}
There's a bunch of answers that do a $path.Split(";") or $path -split ";" that will probably be fine for 99.9% of real-world scenarios, but there's a comment on the accepted answer on a similar question here by Joey that says:
Will fail with quoted paths that contain semicolons.
Basically, it's a bit of an edge case, but this is a perfectly valid PATH on Windows:
c:\temp;"c:\my ; path";c:\windows
so here's a hot mess of code to address that...
function Test-IsInPath
{
param( [string] $Path, [string] $Folder )
# we're going to treat the path as a csv record, but we
# need to know how many columns there are so we can create
# some fake header names. this might give a higher count
# than the real value if there *are* quoted folders with
# semicolons in them, but that's not really an issue
$columnCount = $Path.Length - $Path.Replace(";","").Length
# generate the list of column names. the actual names
# don't matter - it's just so ConvertFrom-Csv treats our
# PATH as a data row instead of a header row
$headers = 0..$columnCount
# parse the PATH as a csv record using ";" as a delimiter
$obj = $path | ConvertFrom-Csv -header $headers -delimiter ";"
# extract an array of all the values (i.e. folders)
# in the record we just parsed
$entries = $obj.psobject.properties.value
# check if the folder we're looking for is in the list
return $entries.Contains($Folder)
}
Whether this is a "better" answer than the simple split approach depends on whether you expect to have quoted folders that contain semicolons in your PATH or not :-)...
Example usage:
PS C:\> Test-IsInPath -Path $env:PATH -Folder "c:\temp"
False
PS C:\> Test-IsInPath -Path "c:\temp;`"c:\my ; path`";c:\windows" -Folder "c:\temp"
True
PS C:\> Test-IsInPath -Path "c:\temp;`"c:\my ; path`";c:\windows" -Folder "c:\my ; path"
True
Note: what this still doesn't solve is paths that end (or don't end) with a trailing "\" - e.g. testing for C:\temp when the PATH contains C:\temp\ and vice versa.
I would go for something like this
function FolderIsInPATH {
param (
[parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]$your_searched_folder
[parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]$Path
)
$Folders = Get-Childitem -Path $Path -Directory
foreach ($Folder in $Folders) {
if ($Folder.Name -eq $your_searched_folder) {
##Folder found
} else {
##folder not found
}
}
}
You can get your PATH using [Environment]::GetEnvironmentVariables()
[Environment]::GetEnvironmentVariables()
Or if you want to get the user environment variables:
[Environment]::GetEnvironmentVariables("User")
Next, get the PATH variable:
$Path = [Environment]::GetEnvironmentVariables().Path # returns the PATH
Then check if the specified folder is in your PATH:
$Path.Contains($Path_to_directory + ";")
The function put together:
function FolderIsInPath($Path_to_directory) {
return [Environment]::GetEnvironmentVariables("User").Path.Contains($Path_to_directory + ";")
}
However, this function is case-sensitive. You can use String.ToLower() to make it not case-sensitive.
function FolderIsInPath($Path_to_directory) {
return [Environment]::GetEnvironmentVariables("User").Path.ToLower().Contains($Path_to_directory.ToLower() + ";")
}
Now call your function like this:
FolderIsInPath("C:\\path\\to\\directory")
Note that the path must be absolute.
As pointed out in mclayton's comment, this function won't work for the last path variable. To address this issue, simply add a ; to the end of the path. Your function would now look like this.
function FolderIsInPath($Path_to_directory) {
return [Environment]::GetEnvironmentVariables("User").Path.ToLower() + ";".Contains($Path_to_directory.ToLower() + ";")
}

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')

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 }

Powershell UNC Path not supported exception

Well I've struggled long enough with this one. I have a project to compare two folders, one on each of two servers. We are comparing files on the source server with those on the target server and will create a list of the files from the source that will need to be refreshed once an update is completed on the target server.
Here's my script (many thanks to http://quickanddirtyscripting.wordpress.com for the original) :
param ([string] $src,[string] $dst)
function get-DirHash()
{
begin
{
$ErrorActionPreference = "silentlycontinue"
}
process
{
dir -Recurse $_ | where { $_.PsIsContainer -eq $false -and ($_.Name -like "*.js" -or $_.Name -like "*.css"} | select Name,FullName,#{Name="SHA1 Hash"; Expression={get-hash $_.FullName -algorithm "sha1" }}
}
end
{
}
}
function get-hash
{
param([string] $file = $(throw 'a filename is required'),[string] $algorithm = 'sha256')
try
{
$fileStream = [system.io.file]::openread((resolve-path $file));
$hasher = [System.Security.Cryptography.HashAlgorithm]::create($algorithm);
$hash = $hasher.ComputeHash($fileStream);
$fileStream.Close();
}
catch
{
write-host $_
}
return $hash
}
Compare-Object $($src | get-DirHash) $($dst | get-DirHash) -property #("Name", "SHA1 Hash")
Now for some reason if I run this against local paths say c:\temp\test1 c:\temp\test2 it works fine, but when I run it using UNC paths between two servers I get
Exception calling "OpenRead" with "1" argument(s): "The given path's format is not supported."
Any help with this would be greatly appreciated. The end result should be a list of files, but for some reason it doesn't like the UNC path.
The script name is compare_js_css.ps1 and is called as such:
.\compare_js_css.ps1 c:\temp\test1 c:\temp\test2 <-- This works
.\compare_js_css.ps1 \\\\devserver1\c$\websites\site1\website \\\\devserver2\c$\websites\site1\website <-- Returns the aforementioned exception.
Why?
This gives the path you are after without the Microsoft.PowerShell.Core\FileSystem:::
(Resolve-Path $file).ProviderPath
No need to use a string replace.
OpenRead supports UNC paths. Resolve-Path returns you an object. Use (Resolve-Path MyFile.txt).Path.Replace('Microsoft.PowerShell.Core\FileSystem::', '') as the argument for OpenRead. The path returned from Resolve-Path when using UNC paths includes PowerShell's fully qualified schema which contains a header which is unsupported by the OpenRead method so it needs to be omitted.
Use the Convert-Path cmdlet, which will provide you with the path in the 'regular' UNC form. This will be required any time you use any shell commands, or need to pass an entire path to a .Net method etc...
See https://technet.microsoft.com/en-us/library/ee156816.aspx