How can I get the powershell "mkdir" command to act exactly like Linux's mkdir -p command?
Under Linux, mkdir -p will create nested directories, but only if they don't exist already. For example, suppose you have a directory /foo that you have write permissions for. mkdir -p /foo/bar/baz creates bar and baz within bar under existing /foo. You run the same command over again, you will not get an error, but nothing will be created.
You can ignore errors in PowerShell with the -ErrorAction SilentlyContinue parameter (you can shorten this to -ea 0). The full PowerShell command is
New-Item /foo/bar/baz -ItemType Directory -ea 0
You can shorten this to
md /foo/bar/baz -ea 0
(You can also type mkdir instead of md if you prefer.)
Note that PowerShell will output the DirectoryInfo object it creates when using New-Item -ItemType Directory (or the md or mkdir aliases). If you don't want any output, you can pipe to Out-Null.
The PowerShell equivalent of Unix
mkdir -p ...
is
$null = New-Item -Type Directory -Force ...
-Force, like -p, implements desired-state logic, which ensures two things:
It creates intermediate directories in the target directory path that may not exist yet on demand (New-Item -Type Directory, unlike Unix mkdir, even does that by default).
It succeeds if the target directory already exists.
One crucial difference: Unless an error occurs:
mkdir -p produces no output.
By contrast, New-Item -Type Directory outputs a [System.IO.DirectoryInfo] instance representing the target directory. Thus, to emulate the behavior of mkdir -p, this output must be discarded, which is best done by assigning to $null ($null = New-Item ...)
Caveat:
On Windows, mkdir is a built-in wrapper function that passes arguments through to New-Item -Type Directory
On Unix-like platforms - in the cross-platform PowerShell [Core] v6+ edition - mkdir is not a built-in command, and instead defers to the platform-native external mkdir utility.
Thus, if your scripts need to be cross-platform, use New-Item explicitly.
Note: If you omit -Force and simply ignore errors, as shown in Bill Stewart's answer (with -ErrorAction SilentlyContinue or -ErrorAction Ignore), you get similar behavior, except that the output behavior will vary situationally:
If the target directory already exists, no output is produced (because an error occurs that is ignored), whereas if the target directory is created, a [System.IO.DirectoryInfo] instance representing it is returned.
A fundamental difference between New-Item -Type Directory and Unix mkdir is that only the former creates intermediate directories on demand by default.
mkdir only does so with -p.
New-Item -Path "c:\some\folder\path" -ItemType Directory
What about using mkdir with -Force? That seems to work for me, it will create all the directories from the specified path, and won't result in an error if they already exist.
mkdir path/to/my/target/dir -force
Results in:
PS > mkdir path/to/my/target/dir -force
Directory: C:\Users\Sam\path\to\my\target
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 25/09/2020 13:18 dir
# We can check that all directories have been created
PS > tree path
C:\USERS\SAM\PATH
└───to
└───my
└───target
└───dir
# Let's run again now that the directories already exist
PS > mkdir path/to/my/target/dir -force
Directory: C:\Users\Sam\path\to\my\target
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 25/09/2020 13:20 dir
# And check the result of the previous command
PS > $?
True
Related
I have a powershell script to install a very large application (15gb source media) from a location its been delivered to on the C drive.
At the end of the script, to ensure that the software is installed I perform a test-path of the HKLM Microsoft Windows CurrentVersion Uninstall path for the GUID, and if successful, clear the source media from the C drive.
If (Test-Path("HKLM:pathname")) { Remove-Item $path -force -recurse }
The problem I have is that the above command works via Powershell ISE when run individually. It knows the key exists so should perform Remove-Item. When run as a script, or via a deployment mechanism, it will not remove the folder.
I have even gone further and used:
GCI $Path -Recurse | Remove-Item -force -recurse
... to no avail.
Prior to introducing the Test-Path, I only had the Remove-Item $Path -force -recurse and this worked!!
So despite Test-Path correctly judging, it appears to prevent Remove-Item from doing anything. (I wrote to a log file to check the If routing)
Any thoughts? Sorry for any typos, I did not copy / paste any part of the script.
If you can't delete the key immediately after, that probably means it has a write lock that's been applied by the Test-Path cmdlet.
Try finding out if a lock exist using Sysinternals Handle command and then release it using the handle.exe -c argument, referencing the hexadecimal number.
Make sure the format used in $path matches the format used by handle.exe
$lock = & handle.exe -nobanner -a -p ($PID) ($path)
if (-not ($lock -like '*No matching handles found*')){
& handle.exe -nobanner -p ($PID) -c ($lock[0].split(':')[0].Trim(' ')) -y
}
This will only work if you have the permission to close handles.
I found that there are two different cmdlets : New-Item and mkdir, firstly I was thinking that mkdir is one of aliases of New-Item, but it is not:
Try to get aliases of it, it is md for mkdir and ni for New-Item :
So I am a little bit confused, what the difference between that cmdlets, because powershell reference gives me almost the same pages: mkdir, New-Item
But New-Item is in Microsoft.PowerShell.Management and mkdir in Microsoft.PowerShell.Core , but the do the same(or not?)! Why there are two same cmdlets in powershell?
New-Item is a cmdlet, defined in an assembly, which creates new objects - both files and directories. mkdir is a function which calls New-Item to create directories specifically. It is provided for convenience to shell users who are familiar with Windows CMD or unix shell command mkdir
To see the definition of mkdir use Get-Content Function:\mkdir. You can see that it calls New-Item under the covers, after some parameter and pipeline management. Using PS 5.0:
$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('New-Item', [System.Management.Automation.CommandTypes]::Cmdlet)
$scriptCmd = {& $wrappedCmd -Type Directory #PSBoundParameters }
Both of the following commands will create a new directory named foo in the root of C:\. The second form is familiar to people coming from other shells (and shorter to type). The first form is idiomatic PowerShell.
PS> New-Item -Path C:\foo -Type Directory
PS> mkdir C:\foo
Because mkdir hardcodes the -Type Directory parameter, it can only be used to create directories. There is no equivalent mkfile built-in function. To create files, use New-Item -Type File, or another cmdlet such as Out-File.
When I use Set-Location (aka cd) to change the current directory in a PowerShell window, but deliberately avoid auto-complete and type the name in the "wrong" case...
PS C:\> Set-Location winDOWs
...then Get-Location (aka pwd) will return that "wrong" path name:
PS C:\winDOWs> Get-Location
Path
----
C:\winDOWs
This causes problems with svn info:
PS C:\svn\myDir> svn info --show-item last-changed-revision
2168
PS C:\svn\myDir> cd ..\MYDIR
PS C:\svn\MYDIR> svn info --show-item last-changed-revision
svn: warning: W155010: The node 'C:\svn\MYDIR' was not found.
svn: E200009: Could not display info for all targets because some targets don't exist
As you can see svn info fails when the user doesn't type the name of the working copy directory "myDir" with the correct letter case when cd'ing into it.
Is there a way to solve this? I could not find a suitable parameter of svn info.
Another option could be to overwrite PowerShell's cd alias and make sure the letter case of the typed path is fixed before actually cd'ing, but how to accomplish that? Resolve-Path, for example also returns the "wrong" directory name.
Something like this might work for you:
Set-Location C:\winDOWs\sysTEm32
$currentLocation = (Get-Location).Path
$folder = Split-Path $currentLocation -Leaf
$casedPath = ([System.IO.DirectoryInfo]$currentLocation).Parent.GetFileSystemInfos($folder).FullName
# if original path and new path are equal (case insensitive) but are different with case-sensitivity. cd to new path.
if($currentLocation -ieq $casedPath -and $currentLocation -cne $casedPath)
{
Set-Location -LiteralPath $casedPath
}
This will give you the proper casing for the "System32" portion of the path. You will need to recursively call this piece of code for all pieces of the path, e.g. C:\Windows, C:\Windows\System32, etc.
Final recursive function
Here you go:
function Get-CaseSensitivePath
{
param([System.IO.DirectoryInfo]$currentPath)
$parent = ([System.IO.DirectoryInfo]$currentPath).Parent
if($null -eq $parent)
{
return $currentPath.Name
}
return Join-Path (Get-CaseSensitivePath $parent) $parent.GetDirectories($currentPath.Name).Name
}
Example:
Set-Location (Get-CaseSensitivePath C:\winDOWs\sysTEm32)
I know that I can use DIR to list the directory and MKDIR to create a new one. However, I learned today that one can (should?) use New-Item -ItemType Directory "c:\pip" instead, which, indeed, looks much more PowerShellish.
I'm not getting any hits when googling for the equivalent of DIR, though. Is DIR just DIR?
dir | ls == get-childitem.
You can figure out aliases using the get-alias command: get-alias dir. Aliases are used fairly frequently, for example, gci is an alias for get-childitem as well.
I am having a PowerShell script which is walking a directory tree, and sometimes I have auxiliary files hardlinked there which should not be processed. Is there an easy way of finding out whether a file (that is, System.IO.FileInfo) is a hard link or not?
If not, would it be easier with symbolic links (symlinks)?
Try this:
function Test-ReparsePoint([string]$path) {
$file = Get-Item $path -Force -ea SilentlyContinue
return [bool]($file.Attributes -band [IO.FileAttributes]::ReparsePoint)
}
It is a pretty minimal implementation, but it should do the trick. Note that this doesn't distinguish between a hard link and a symbolic link. Underneath, they both just take advantage of NTFS reparse points, IIRC.
If you have Powershell 5+ the following one-liner recursively lists all file hardlinks, directory junctions and symbolic links and their targets starting from d:\Temp\:
dir 'd:\Temp' -recurse -force | ?{$_.LinkType} | select FullName,LinkType,Target
Output:
FullName LinkType Target
-------- -------- ------
D:\Temp\MyJunctionDir Junction {D:\exp\junction_target_dir}
D:\Temp\MySymLinkDir SymbolicLink {D:\exp\symlink_target_dir}
D:\Temp\MyHardLinkFile.txt HardLink {D:\temp\MyHardLinkFile2.txt, D:\exp\hlink_target.xml}
D:\Temp\MyHardLinkFile2.txt HardLink {D:\temp\MyHardLinkFile.txt, D:\exp\hlink_target.xml}
D:\Temp\MySymLinkFile.txt SymbolicLink {D:\exp\symlink_target.xml}
D:\Temp\MySymLinkDir\MySymLinkFile2.txt SymbolicLink {D:\temp\normal file.txt}
If you care about multiple targets for hardlinks use this variation which lists targets tab-separated:
dir 'd:\Temp' -recurse -force | ?{$_.LinkType} | select FullName,LinkType,#{ Name = "Targets"; Expression={$_.Target -join "`t"} }
You may need administrator privileges to run this script on say C:\.
Utilize Where-Object to search for the ReparsePoint file attribute.
Get-ChildItem | Where-Object { $_.Attributes -match "ReparsePoint" }
For those that want to check if a resource is a hardlink or symlink:
(Get-Item ".\some_resource").LinkType -eq "HardLink"
(Get-Item ".\some_resource").LinkType -eq "SymbolicLink"
My results on Vista, using Keith Hill's powershell script to test symlinks and hardlinks:
c:\markus\other>mklink symlink.doc \temp\2006rsltns.doc
symbolic link created for symlink.doc <<===>> \temp\2006rsltns.doc
c:\markus\other>fsutil hardlink create HARDLINK.doc \temp\2006rsltns.doc
Hardlink created for c:\markus\other\HARDLINK.doc <<===>> c:\temp\2006rsltns.doc
c:\markus\other>dir
Volume in drive C has no label.
Volume Serial Number is C8BC-2EBD
Directory of c:\markus\other
02/12/2010 05:21 PM <DIR> .
02/12/2010 05:21 PM <DIR> ..
01/10/2006 06:12 PM 25,088 HARDLINK.doc
02/12/2010 05:21 PM <SYMLINK> symlink.doc [\temp\2006rsltns.doc]
2 File(s) 25,088 bytes
2 Dir(s) 6,805,803,008 bytes free
c:\markus\other>powershell \script\IsSymLink.ps1 HARDLINK.doc
False
c:\\markus\other>powershell \script\IsSymLink.ps1 symlink.doc
True
It shows that symlinks are reparse points, and have the ReparsePoint FileAttribute bit set, while hardlinks do not.
here is a one-liner that checks one file $FilePath and returns if it is a symlink or not, works for files and directories
if((Get-ItemProperty $FilePath).LinkType){"symboliclink"}else{"normal path"}
Just want to add my own two cents, this is a oneliner function which works perfectly fine for me:
Function Test-Symlink($Path){
((Get-Item $Path).Attributes.ToString() -match "ReparsePoint")
}
The following PowerShell script will list all the files in a directory or directories with the -recurse switch. It will list the name of the file, whether it is a regular file or a hardlinked file, and the size, separated by colons.
It must be run from the PowerShell command line. It doesn't matter which directory you run it from as that is set in the script.
It uses the fslink utility shipped with Windows and runs that against each file using the hardlink and list switches and counts the lines of output. If two or greater it is a hardlinked file.
You can of course change the directory the search starts from by changing the c:\windows\system in the command. Also, the script simply writes the results to a file, c:\hardlinks.txt. You can change the name or simply delete everything from the > character on and it will output to the screen.
Get-ChildItem -path C:\Windows\system -file -recurse -force |
foreach-object {
if ((fsutil hardlink list $_.fullname).count -ge 2) {
$_.PSChildname + ":Hardlinked:" + $_.Length
} else {
$_.PSChildname + ":RegularFile:" + $_.Length
}
} > c:\hardlinks.txt