Powershell Change path in variable - powershell

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

Related

Powershell: Incrementing number while ignoring string tag

this is my first stack question so go easy on me.
Currently working on a project to create a new folder on a network drive by incrementing off of the previous folders version number.
For example:
5.2.0.0110 -> 5.2.0.0111
Here is my current powershell solution that does the trick:
$SourceFolder = "\\corpfs1\setup\ProtectionSuite\Version 5.2.0.x\5.2.0.0001"
$DestinationFolder = "\\corpfs1\setup\ProtectionSuite\Version 5.2.0.x"
$msiSourceFolder = "\\SourceMsiPath"
$exeSourceFolder = "\\SourceExePath"
if (Test-Path $SourceFolder)
{
$latest = Get-ChildItem -Path $DestinationFolder| Sort-Object Name -Descending | Select-Object -First 1
#split the latest filename, increment the number, then re-assemble new filename:
$newFolderName = $latest.BaseName.Split('.')[0] + "." + $latest.BaseName.Split('.')[1] + "."+ $latest.BaseName.Split('.')[2] + "." + ([int]$latest.BaseName.Split('.')[3] + 1).ToString().PadLeft(4,"0")
New-Item -Path $DestinationFolder"\"$newFolderName -ItemType Directory
Copy-Item $msiSourceFolder -Destination $DestinationFolder"\"$newFolderName
Copy-Item $exeSourceFolder -Destination $DestinationFolder"\"$newFolderName
}
However, one thing that this does not account for is version numbers with string at the end. This solution attempts to covert the string -> int which fails. Some of the folders have strings as they are for internal releases so there is no way to just change my naming semantics.
For example: 5.2.0.1234 (eng) -> 5.2.0.1235
I would like to ignore any text after the last four digits and increment as shown in the example above. If anyone has a suggestion I am all ears! Thank you.
You can do:
$version = ($latest.BaseName -replace '^((?:\d+\.){3}\d{4}).*', '$1').Split('.')
$version[-1] = '{0:D4} -f ([int]$version[-1] + 1)
$newFolderName = $version -join '.'
# '5.2.0.0110 (eng)' --> '5.2.0.0111'
As per your comment, you should use Join-Path for constructing the full target path:
$targetPath = Join-Path -Path $DestinationFolder -ChildPath $newFolderName
$null = New-Item -Path $targetPath -ItemType Directory -Force
Copy-Item $msiSourceFolder -Destination $targetPath
Copy-Item $exeSourceFolder -Destination $targetPath
Assuming that your folder names contain only one 4-digit sequence preceded by a ., it is simpler to match and replace only it, using the regular-expression-based -replace operator with a script block-based substitution:
Update:
A later clarification revealed that the post-version suffix in the input string should be (b) removed from the output rather than (a) just ignored for the purpose of incrementing while getting preserved in the output - see the bottom section for a solution to (b).
SOLUTION (a): If the post-version suffix should be preserved:
In PowerShell (Core) v6.1+:
# Replace the sample value '5.2.0.1234 (eng)' with the following in your code:
# $newFolderName = $latest.BaseName [-replace ...]
'5.2.0.1234 (eng)' -replace '(?<=\.)\d{4}', { '{0:0000}' -f (1 + $_.Value) }
The above yields 5.2.0.1235 (eng) - note the incremented last version-number component and the preservation of the suffix.
In Windows PowerShell (versions up to 5.1), where script block-based substitutions aren't supported, direct use of the underlying .NET API is required:
[regex]::Replace('5.2.0.0110 (eng)', '(?<=\.)\d{4}', { '{0:0000}' -f (1 + $args[0].Value) })
Explanation:
(?<=\.)\d{4} is a regex (regular expression) that matches a literal . (\.) inside a look-behind assertion ((?<=...)), followed by 4 ({4}) digits (\d). The look-behind assertion ensures that the literal . isn't included in the text captured by the match.
The script block ({ ... }) receives information about (each) match, as a System.Text.RegularExpressions.Match instance, via the automatic $_ variable in the PowerShell (Core) solution, via the automatic $args variable in the Windows PowerShell solution with the direct .NET call.
The script block's output (return value) is used to replace the matched text:
'{0:0000}' -f ... uses -f, the format operator, to format the RHS with 4-digit 0-padding.
(1 + $_.Value) / (1 + $args[0].Value) adds 1 to the 4-digit sequence captured by the match, which is implicitly converted to a number due to the LHS of the + operation being a number.
SOLUTION (b): If the post-version suffix should be removed:
In PowerShell (Core) v6.1+:
'5.2.0.1234 (eng)' -replace '\.(\d{4}).*', { '.{0:0000}' -f (1 + $_.Groups[1].Value) }
The above yields 5.2.0.1235 - note the incremented last version-number component and the absence of the suffix.
In Windows PowerShell:
[regex]::Replace('5.2.0.1234 (eng)', '\.(\d{4}).*', { '.{0:0000}' -f (1 + $args[0].Groups[1].Value) })

PowerShell: Add every sub-directory to path

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
)"
}

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.

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.

Get a specific folder name from path

How to get the 4th folder name and store it in a variable while looping through the files stored in a parent folder. For example, if the path is
C:\ParentFolder\Subfolder1\subfolder2\subfolder3\file.extension
C:\ParentFolder\Subfolder1\subfolder2\subfolder4\file.extension
C:\ParentFolder\Subfolder1\subfolder2\subfolder5\file.extension
then subfolder2 name should be stored in a variable. Can any one please help me on this?
get-childitem $DirectorNane -Recurse | Where-Object{!($_.PSIsContainer)} | % {
$filePath = $_.FullName
#Get file name
$fileName = Split-Path -Path $filePath -Leaf
} $FileI = Split-Path -Path $filePath -Leaf
Thanks in advance!
You can use the -split operator on the $filePath variable to get what you want.
$split = $filePath -split '\\'
$myvar = $split[3]
We use the double backslash to split with, because the first slash escapes the slash character to split the path by. Then, we can reference the part of the path we want in the array that gets generated in the "split" variable.
Additionally, you can solve this with a one liner using the following code:
$myvar = $filepath.split('\')[3]
This would ensure that you're always getting the fourth element in the array, but is a little less flexible since you can't specify what exactly you want based on conditions with additional scripting.
If you are asking how to get the parent directory of the directory containing a file, you can call Split-Path twice. Example:
$filePath = "C:\ParentFolder\Subfolder1\subfolder2\subfolder3\file.extension"
$parentOfParent = Split-Path (Split-Path $filePath)
# $parentOfParent now contains "C:\ParentFolder\Subfolder1\subfolder2"