Change path separator in Windows PowerShell - powershell

Is it possible to get PowerShell to always output / instead of \? For example, I'd like the output of get-location to be C:/Documents and Settings/Administrator.
Update
Thanks for the examples of using replace, but I was hoping for this to happen globally (e.g. tab completion, etc.). Based on Matt's observation that the separator is defined by System.IO.Path.DirectorySeparatorChar which appears in practice and from the documentation to be read-only, I'm guessing this isn't possible.

It's a good question. The underlying .NET framework surfaces this as System.IO.Path.DirectorySeparatorChar, and it's a read/write property, so I figured you could do this:
[IO.Path]::DirectorySeparatorChar = '/'
... and that appears to succeed, except if you then type this:
[IO.Path]::DirectorySeparatorChar
... it tells you that it's still '\'. It's like it's not "taking hold". Heck, I'm not even sure that PowerShell honours that particular value even if it was changing.
I thought I'd post this (at the risk of it not actually answering your question) in case it helps someone else find the real answer. I'm sure it would be something to do with that DirectorySeparatorChar field.

Replace "\" with "/".
PS C:\Users\dance2die> $path = "C:\Documents and Settings\Administrator"
PS C:\Users\dance2die> $path.Replace("\", "/")
C:/Documents and Settings/Administrator

You could create a filter (or function) that you can pipe your paths to:
PS C:\> filter replace-slash {$_ -replace "\\", "/"}
PS C:\> Get-Location | replace-slash
C:/

Related

PowerShell Get-VHD "is not an existing virtual hard disk file"

When creating a new VM in Hyper-V, to keep things organized, I use a particular naming convention when creating the associated VHDX files. The naming convention is the VMs FQDN followed by the SCSI controller attachment point followed by what the name of the drive is called or used for inside of the VM. I encapsulate the SCSI and Name parameters inside smooth and square brackets respectively. I find this tends to make things a little bit easier from a human perspective to match the VHDX files in Hyper-V to what the VM sees internally when needing to do maintenance tasks. It has also helped with scripting in the past. An example file name would look as follows...
servername.example.com(0-0)[OS].vhdx
This has worked well for quite some time, but recently I tried to run some PowerShell commands against the VHDX files and ran across a problem. Apparently the square brackets for the internal VM name are being parsed as RegEx or something inside of the PowerShell commandlet (I'm honestly just guessing on this). When I try to use Get-VHD on a file with the above naming convention it spits out an error as follows:
Get-VHD : 'E:\Hyper-V\servername.example.com\Virtual Hard Disks\servername.example.com(0-0)[OS].vhdx' is not an existing virtual hard disk file.
At line:1 char:12
+ $VhdPath | Get-VHD
+ ~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Get-VHD], VirtualizationException
+ FullyQualifiedErrorId : InvalidParameter,Microsoft.Vhd.PowerShell.Cmdlets.GetVHD
If I simply rename the VHDX file to exclude the "[OS]" portion of the naming convention the command works properly. The smooth brackets for the SCSI attachment point don't seem to bother it. I've tried doing a replace command to add a backtick ''`'' in front of the brackets to escape them, but the same error results. I've also tried double backticks to see if passing in a backtick helped... that at least showed a single backtick in the error it spat out. Suspecting RegEx, I tried the backslash as an escape character too... which had the interesting effect of converting all the backslashes in the file path into double backslashes in the error message. I tried defining the path variable via single and double quotes without success. I've also tried a couple of different ways of obtaining it via pipeline such as this example...
((Get-VM $ComputerName).HardDrives | Select -First 1).Path | Get-VHD
And, for what it's worth, as many VMs as I am attempting to process... I need to be able to run this via pipeline or some other automation scriptable method rather than hand coding a reference to each VHDX file.
Still thinking it may be something with RegEx, I attempted to escape the variable string with the following to no avail:
$VhdPathEscaped = [System.Text.RegularExpressions.Regex]::Escape($VhdPath)
Quite frankly, I'm out of ideas.
When I first ran across this problem was when I tried to compact a VHDX file with PowerShell. But, since the single VM I was working with needed to be offline for that function to run anyway, rather than fight the error with the VHDX name, I simply renamed it, compacted it, and reset the name back. However, for the work I'm trying to do now, I can't afford to take the VM offline as this script is going to run against a whole fleet of live VMs. So, I need to know how to properly escape those characters so the Get-VHD commandlet will accept those file names.
tl;dr:
A design limitation of Get-VHD prevents it from properly recognizing VHD paths that contain [ and ] (see bottom section for details).
Workaround: Use short (8.3) file paths assuming the file-system supports them:
$fso = New-Object -ComObject Scripting.FileSystemObject
$VhdPath |
ForEach-Object { $fso.GetFile((Convert-Path -LiteralPath $_)) } |
Get-VHD
Otherwise, your only options are (as you report, in your case the VHDs are located on a ReFS file-system, which does not support short names):
Rename your files (and folders, if applicable) to not contain [ or ].
Alternatively, if you can assume that your VHDs are attached to VMs, you can provide the VM(s) to which the VHD(s) of interests are attached as input to Get-VHD, via Get-VM (you may have to filter the output down to only the VHDs of interest):
(Get-VM $vmName).Id | Get-VHD
Background information:
It looks like Get-VHD only has a -Path parameter, not also a -LiteralPath parameter, which looks like a design flaw:
Having both parameters is customary for file-processing cmdlets (e.g. Get-ChildItem):
-Path accepts wildcard expressions to match potentially multiple files by a pattern.
-LiteralPath is used to pass literal (verbatim) paths, to be used as-is.
What you have is a literal path that happens to look like a wildcard expression, due to use of metacharacters [ and ]. In wildcard contexts, these metacharacter must normally be escaped - as `[ and `] - in order to be treated as literals, which the following (regex-based) -replace operation ensures[1] (even with arrays as input).
Unfortunately, this appears not to be enough for Get-VHD. (Though you can verify that it works in principle by piping to Get-Item instead, which also binds to -Path).
Even double `-escaping (-replace '[][]', '``$&') doesn't work (which is - unexpectedly required in come cases - see GitHub issue #7999).
# !! SHOULD work, but DOES NOT
# !! Ditto for -replace '[][]', '``$&'
$VhdPath -replace '[][]', '`$&' | Get-VHD
Note: Normally, a robust way to ensure that a cmdlet's -LiteralPath parameter is bound by pipeline input is to pipe the output from Get-ChildItem or Get-Item to it.[2] Given that Get-VHD lacks -LiteralPath, this is not an option, however:
# !! DOES NOT HELP, because Get-VHD has no -LiteralPath parameter.
Get-Item -LiteralPath $VhdPath | Get-VHD
[1] See this regex101.com page for an explanation of the regex ($0 is an alias of $& and refers to the text captured by the match at hand, i.e. either [ or ]). Alternatively, you could pass all paths individually to the [WildcardPattern]::Escape() method (e.g., [WildcardPattern]::Escape('a[0].txt') yields a`[0`].txt.
[2] See this answer for the specifics of how this binding, which happens via the provider-supplied .PSPath property, works.
Ok... So, I couldn't get the escape characters to be accepted by Get-VHD... be it by hand or programmatically. I gave it a go of passing it on the pipeline using Get-ChildItem too without success. However... I did manage to find an alternative for my particular use case. In addition to a path to a VHDX file, the Get-VHD command will also accept vmid, and disknumber as parameters. So, not that it's the way I wanted to go about obtaining what I need (because this method spits out info on all the attached drives), I can still manage to accomplish the task at hand by using the following example:
Get-VM $ComputerName | Select-Object -Property VMId | Get-VHD
By referencing them in this manner the Get-VHD commandlet is happy. This works for today's problem only because the VHDX files in question are attached to VMs. However, I'll still need to figure out about referencing unattached files at some point in the future. Which... Maybe ultimately require a slow and painful renaming of all the VHDX files to not use the square brackets in their name.

Q: Basic PowerShell Script Issue: “Expressions are only allowed as the first element of a pipeline”

I know well, this issue has already been discussed a lot. But even though I am unable to solve it myself, since I only use powershell scripting rarely...
I have the following commands, that I need to execute as a ".bat" file. If I just execute it within a command window, all works fine. But if I execute it within a ".bat" file, I get the error from the title...
powershell -Command "&{$devices = gwmi Win32_USBControllerDevice |%{[wmi] ($_.Dependent)}|select DeviceID;$devices.DeviceID | %{Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Enum\$_"}| select FriendlyName,HardwareID}"
Within the ".bat" file I added an additional line with "pause", that seems not to be the cause of the problem...
I would be very happy if someone more advanced in scripting could tell me what 1 to 10 chars need to be added/changed in the command to make it work ;-)
Big thanks in advance!!!
Regards,
Erik
Based upon your response to my initial comment, the reason for that error is that you're using the % alias for ForEach-Object, and in a batch file, the % character needs to be escaped using another % character.
Including the nested double-quote escaping with backward slashes, I've already advised, your resulting command would therefore be:
powershell -Command "&{$devices = gwmi Win32_USBControllerDevice |%%{[wmi] ($_.Dependent)}|select DeviceID;$devices.DeviceID | %%{Get-ItemProperty -Path \"HKLM:\SYSTEM\CurrentControlSet\Enum\$_\"}| select FriendlyName,HardwareID}"
Tested result example:
If I was doing this I'd probably have done it a little differently:
%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe -NoProfile -Command^
"((Get-CimInstance Win32_USBControllerDevice).Dependent).DeviceID |"^
"ForEach-Object {"^
"Get-ItemProperty -Path \"HKLM:\SYSTEM\CurrentControlSet\Enum\$_\""^
"} | Select-Object FriendlyName, HardwareID"
And using shortened commands/aliases:
PowerShell -NoP "((gcim Win32_USBControllerDevice).Dependent).DeviceID|%%{gp \"HKLM:\SYSTEM\CurrentControlSet\Enum\$_\"}|Select FriendlyName,HardwareID"

Split directory path powershell

I want to split directory path using power shell. My full path is D:\data\path1\path2\abc.txt and i want to split it to path2\abc.txt.
Can someone let me know how can I do that.
$last2parts = "D:\data\path1\path2\abc.txt".Split("\") | Select-Object -Last 2
$last2parts -join "\"
In reply to your comment on another solution try this. Just remove your constant d:\data\path1. Then perform the split
$last2parts = "D:\data\path1\path2\abc.txt".Replace("D:\data\path1","")
$last2parts =$last2parts.Split("\") | Select-Object -Last 2
$last2parts -join "\"
Or try perhaps this if you want everything after D:\data\path1
$lastparts = "D:\data\path1\path2\abc.txt".Replace("D:\data\path1","")
$lastparts =$lastparts.Split("\")
$lastparts -join "\"
$PathAsString = "D:\data\path1\path2\abc.txt"
[System.IO.Path]::Combine($(Split-Path -leaf $(Split-Path $PathAsString)),$(Split-Path -leaf $PathAsString))
Uses the system's delimiter rather than specifying Windows' '\' character.
Honestly if I knew this was only ever going to run on Windows systems I'd go with #ChiliYago's answer since you can't put the path delimiter character in a file or directory name like you can in Linux.
Sorry for the super late post! I think you may have got the fix. But here is something you can try.
You can try using the substring() function as well.
$fullPath = "D:\data\path1\path2\abc.txt"
$rootPath = "D:\data\path1\"
$filePath = $fullPath.substring($rootPath.length, ($fullPath.length - $rootPath.length) )
Something like this may help you.
Here on Stack Overflow when you ask a question, please post some code that you've tried already and the results you're getting. We help you with code you've already tried, we aren't here to write it for you (:
However... I would start here: Link
That will get you started. You can also do the following in a console to get some info on the command Split-Path
Get-Help Split-Path -Example
After you put something together on your own, you can edit your post to include what you've tried and then you will be more likely to get an accurate response for your particular situation. Good luck and good coding!

Microsoft's Consistency in PowerShell CmdLet Parameter Naming

Let's say I wrote a PowerShell script that includes this commmand:
Get-ChildItem -Recurse
But instead I wrote:
Get-ChildItem -Re
To save time. After some time passed and I upgraded my PowerShell version, Microsoft decided to add a parameter to Get-ChildItem called "-Return", that for example returns True or False depending if any items are found or not.
In that virtual scenario, do I have I to edit all my former scripts to ensure that the script will function as expected? I understand Microsoft's attempt to save my typing time, but this is my concern and therefore I will probably always try to write the complete parameter name.
Unless of course you know something I don't. Thank you for your insight!
This sounds more like a rant than a question, but to answer:
In that virtual scenario, do I have I to edit all my former scripts to ensure that the script will function as expected?
Yes!
You should always use the full parameter names in scripts (or any other snippet of reusable code).
Automatic resolution of partial parameter names, aliases and other shortcuts are great for convenience when using PowerShell interactively. It lets us fire up powershell.exe and do:
ls -re *.ps1|% FullName
when we want to find the path to all scripts in the profile. Great for exploration!
But if I were to incorporate that functionality into a script I would do:
Get-ChildItem -Path $Home -Filter *.ps1 -Recurse |Select-Object -ExpandProperty FullName
not just for the reasons you mentioned, but also for consistency and readability - if a colleague of mine comes along and maybe isn't familiar with the shortcuts I'm using, he'll still be able to discern the meaning and expected output from the pipeline.
Note: There are currently three open issues on GitHub to add warning rules for this in PSScriptAnalyzer - I'm sure the project maintainers would love a hand with this :-)

What exactly does the Resolve-Path cmdlet do?

I've started working with Powershell lately on my Windows 10 system. I've created a profile file, and I wanted to put in a variable which links to a folder in my documents section. However, I didn't want to hard-code the path because I knew I'd need to go in and re-write it if my profile name got changed in a hard drive transfer like it did the last time I sent it in for repairs. So my first thought was to put in something like this:
$Var = $(~\Documents\Folder)
But that spat back an error. Then I learned of the Resolve-Path Cmdlet, which appeared at a glance to be what I needed. However, when I did this:
$Var = $(Resolve-Path ~\Documents\Folder)
... I got this:
>$Var
Path
----
C:\Users\Username\Documents\Folder
>
Which seemed like a problem. However, when I tried to cd $Var, it worked successfully, which confused me greatly. I figured that the extraneous Path header in the result would cause an error.
What exactly does Resolve-Path do, and why does it still get interpreted correctly when passed into cd? And additionally, is there any way to make Resolve-Path not include the extraneous information and return only the expanded path?
The reason you are seeing the header Path is because Resolve-Path is returning an object of type System.Management.Automation.PathInfo and when this is not captured into a variable it is outputted as a string to the console in a readable format IE:
ObjProperty1 ObjProperty2 ObjProperty3 ...
--------- --------- --------- ...
Value1 Value2 Value3 ...
This object works with cd as PowerShell is smart enough to parse the object ($var) before cd is run. PowerShell will return the value of the path property to cd meaning cd will see a string "C:\Users\Username\Documents\Folder" and not the object headers.
If you want to just return the path without the header use the Select cmdlet's -ExpandProperty parameter:
$var = Resolve-Path '~\Documents\Folder' | select -ExpandProperty Path
If you would like more information on Resolve-Path MSDN is a good place to start (link).
Easy Example for a Resolve-Path job,
Let's say you don't know the office version in your computer and want to get the correct path, Resolve-Path can help...
$TestPath = 'C:\Program Files (x86)\Microsoft Office\*\Winword.exe'
$Path = (Resolve-Path $TestPath).Path
Resolve-Path is a good way to find a nested file or folder with wildcards in the middle of the folder path (not just in the path's Leaf).
Best use case I've seen is, when a restore request comes into the SD and the claim is that an entire folder disappeared. Someone probably moved the folder into (under) an adjacent folder in their GUI. Easily find that folder like this:
PS> Resolve-Path 'folderOldLocation\*\folderName'
PS> Resolve-Path 'folderOldLocation\*\*\folderName'
PS> # Going deeper is unlikely to find it
PS> # and will take much longer to return
It's a very quick way to check for a quick fix to an otherwise time-consuming restore request.