Recursive hard link - powershell

With Unix cp you can use the --link option. When used with a folder, it will
hard link the files involved instead of
copying, example
cp --recursive --link foo bar
This can be ideal in certain situations because it is faster than regular
copying. Can anything like this be done with PowerShell?

PowerShell doesn't have support for Symbolic/Hard Links currently. There are improvements on this front coming in PowerShell 5.0. The latest preview (September 2014) includes some of this functionality. You may want to peruse the release notes (docx):
To support symbolic links, *-Item and a few related cmdlets have been extended. Now you can create symbolic links in a single, simple line with New-Item.
An example:
New-Item -ItemType HardLink -Path C:\Temp -Name MyHardLinkFile.txt -Value $pshome\profile.ps1
There isn't an example for Copy-Item, but I assume it would be simple to use this with a recursive Get-ChildItem and pipe the results to New-Item, but you would have to try it yourself.
In the meantime, the PowerShell Community Extensions project has a New-Hardlink cmdlet. From the looks of it, you would have to do as I described above and pipe the results of Get-ChildItem into this cmdlet to create a hardlink for each file.

Related

Apply setCaseSensitiveInfo recursively to all folders and subfolders

I am trying to configure my dotnet core project (in Windows) as "case sensitive", so it behaves as in my production server (linux).
I have found this way of doing it:
fsutil.exe file setCaseSensitiveInfo "C:\my folder" enable
The problem is that this function is not recursive:
The case sensitivity flag only affects the specific folder to which you apply it. It isn’t automatically inherited by that folder’s subfolders.
So I am trying to build a powershell script that applies this to all folders and subfolders, recursively.
I have tried googling something similar and just modifying the command line, but I don't seem to find the corrent keywords. This is the closest that I've gotten to this sort of example.
Correct code:
(Get-ChildItem -Recurse -Directory).FullName | ForEach-Object {fsutil.exe file setCaseSensitiveInfo $_ enable}
Explanation:
NOTE: The code in the answer assumes you're in the root of the directory tree and you want to run fsutil.exe against all the folders inside, as it's been pointed out in the comments (thanks #Abhishek Anand!)
Get-ChildItem -Recurse -Directory will give you list of all folders (recursively).
As you want to pass their full path, you can access it by using .FullName[1] (or more self-explanatory | Select-Object -ExpandProperty FullName ).
Then you use ForEach-Object to run fsutil.exe multiple times. Current file's FullName can be accessed using $_ (this represents current object in ForEach-Object)[2].
Hint:
If you want more tracking of what's currently being processed you can add the following to write the path of currently processed file to the console: ; Write-Host $_ (semicolon ; is to separate from fsutil invocation) as it was pointed out in the comments (thanks Fund Monica's Lawsuit !)
[1] .FullName notation works for PowerShell 3.0 and greater, Select-Object -ExpandProperty FullName is preferred if there's a chance that lower version will be used.
[2] $_ is an alias for $PSItem
(Get-ChildItem -Recurse -Directory).FullName | ForEach-Object {if (-Not ($_ -like '*node_modules*')) { fsutil.exe file setCaseSensitiveInfo $_ enable } }
I modified #robdy's code to allow excluding node_modules. You can replace the "node_modules" bit in the above with anything to exclude filepaths containing it.
If you're working with npm, you probably want to exclude node_modules. #robdy's answer is great, but was taking minutes at a time iterating over every single node package folder even if I didn't have the package installed; given that this is something one might want to run fairly often since directories might be added all the time, and since you probably aren't modifying anything in node_modules, excluding it seems reasonable.
With Cygwin and bash shell, you can do this:
$ find $THEDIR -type d -exec fsutil file setCaseSensitiveInfo "{}" enable \;
It appears that Windows handles the '/' characters output by the find command just fine.
In my case I had to first enable the Linux subsystem before using the fsutil tool. So my steps were:
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux
then restart, and then #robdy 's solution:
(Get-ChildItem -Recurse -Directory).FullName | ForEach-Object {fsutil.exe file setCaseSensitiveInfo $_ enable}
On windows 11, the other answers are not correct, as fsutil requires that the directory is not empty. To overcome this, I created a NEW empty directory, used fsutil file setCaseSensitiveInfo to set the case sensitive flag on the new directory, then MOVED the files from the other directory inside the new one. This works, as the directories are re-created when moved, and new directories inherit the case sensitive flag.

What's the proper way to copy files while preserving folder structure in powershell?

I can never seem to get this right.
I have an existing folder c:\MyApps\Websites\MySite that already has an existing website running in it. I've downloaded the latest bits which are located under c:\temp\MySite\artifacts.
when I try to run this
$source "c:\temp\MySite"
$destination "c:\MyApps\Websites\MySite"
copy-item $source $destination -recurse -force
if c:\MyApps\Websites\MySite already exists then it tries to put it in c:\MyApps\Websites\MySite\artifacts, but if it doesn't already exist it copies it properly. Not sure what's going on here. Any suggestions?
Just use the robocopy command. It's designed for this kind of thing.
robocopy $source $destination /E
It's shipped with Windows 7, so it's an internal command, so it's still a 'Powershell' way, imho, but if you're wanting to use the copy command, then you are very close, but your current implementation grabs the source folder and puts it inside target (ie the result would be C:\target\source\files, not C:\target\files). When you want to make the inside of target look like the inside of source, you need to do:
cp C:\source\* C:\target -r -fo
Notice the *, this grabs the contents of the source folder. If you want target to be cleared first, just do rm -r -fo C:\target\* before the copy.
Powershell doesn't handle long paths, so you will need to use robocopy anyway if you have a long filename or deep folders.

Clean up a remote machine's temporary directory: How do I identify files that are in use? (Powershell)

I have a number of remote machines whose temporary directories get full. (The are Selenium / webdriver grid remotes). I have a powershell script that identifies the files and directories that need to be cleaned. The command in use looks something like this (excluding complexities of the various machines and directories):
gci $env:TEMP -Recurse| Remove-Item -ErrorAction Continue -Recurse
The problem is that this takes far too long when some files are in use. Locally, I could join to the output of handles (parsing would be a little ugly), but that would be more complicated on a remote machine. Among other things, I'd need to verify that WinRM was configured correctly, handles was in path, etc.
Is there a simpler way to identify that a file is in use? Ideally one that can be filtered on via Powershell (which includes .NET). I'm familiar with a variety of other scripting languages (ruby, python, perl).
The best tool I've found for listing open files is the SysInternals tool handle.exe e.g.:
$openFiles = #(handle $env:TEMP | Foreach {($_ -split ": ")[3]} | Select -Unique)

One-line for zipping folder in PowerShell?

I'm looking for the equivalent PowerShell command for:
tar -zcvf tar-archive-name.tar.gz source-folder
Does such a one-liner exist that creates a zip file from a folder recursively adding the contents of the source-folder, that doesn't require a 3rd party library like 7-zip?
Starting with Powershell 5, you can use the built-in Compress-Archive command:
Compress-Archive -Path source-folder -DestinationPath archive-name.zip
If you are using powershell prior to version 5, there is no one liner solution without a third party program like 7-zip. However, you can leverage the built in zipping capabilities of windows explorer as exposed through the COM Shell object to build your own powershell function to do this. This page outlines the details on how to do so.
Actually, there is a one-line equivalent that creates zip archives, and there is even a one-liner to create an exact match, i.e. a tar file. The first place you should look for such a library function is PowerShell Community Extensions, providing a wealth of high-quality library extensions to PowerShell. Unfortunately, a web-search for a specific item, like your zip creation function, would likely not turn up anything because nowhere on the site or in the documentation do they include a list of the cmdlets and functions provided! To find out you have to install the library then do help about_pscx. Sifting through that, here is what you will find relevant to your question:
Write-Zip
Create ZIP format archive files from pipline or parameter input.
Write-GZip
Create GNU ZIP (GZIP) format files from pipeline or parameter input.
Write-Tar
Create Tape Archive (TAR) format files from pipeline or parameter input.
And here, from their embedded examples, are how you might use them:
PS> dir c:\logs\ -rec -inc *.log | write-zip -level 9 -removeoriginal
PS> dir c:\logs\ -rec -inc *.log | write-tar -output logs.tar | write-gzip -level 9 | move-item c:\archived_logs\

PowerShell analog to "dir /a:d" (Win) or "ls -d */" (Bash)

I simply want to list all of the directories under my current working directory, using PowerShell. This is easy from a Bash shell:
ls -d */
or cmd.exe in Windows:
dir /a:d
Using PowerShell however I cannot seem to be able to do it with a single command. Instead the only the I've found that works is:
ls | ? {$_Mode -like "d*"}
That seems way too wordy and involved, and I suspect that I don't need a separate Where clause there. The help for Get-ChildItem doesn't make it clear how to filter on Mode though. Can anyone enlighten me?
This works too:
ls | ?{$_.PsIsContainer}
There is no doubt that it is a little more wordy than bash or cmd.exe. You could certainly put a function and an alias in your profile if you wanted to reduce the verbosity. I'll see if I can find a way to use -filter too.
On further investigation, I don't believe there is a more terse way to do this short of creating your own function and alias. You could put this in your profile:
function Get-ChildContainer
{
param(
$root = "."
)
Get-ChildItem -path $root | Where-Object{$_.PsIsContainer}
}
New-Alias -Name gcc -value Get-ChildContainer -force
Then to ls the directories in a folder:
gcc C:\
This solution would be a little limited since it would not handle any fanciness like -Include, -Exclude, -Filter, -Recurse, etc. but you could easily add that to the function.
Actually, this is a rather naive solution, but hopefully it will head you in the right direction if you decide to pursue it. To be honest with you though I wouldn't bother. The extra verbosity in this one case is more than overcome by the overall greater flexibility of powershell in general in my personal opinion.
Try:
ls | ? {$_.PsIsContainer}
dir -Exclude *.*
I find this easier to remember than
dir | ? {$_.PsIsContainer}
Plus, it is faster to type, as you can do -ex instead of -exclude or use tab to expand it.
You can now use Get-ChildItem -Directory or ls -dir for short. This has existed at least since PowerShell 3.0 according to Microsoft's documentation.
You can check old post on PowerShell team blog: http://blogs.msdn.com/powershell/archive/2009/03/13/dir-a-d.aspx
I came to this thread because I'm trying to figure out how "FOR /D" works. Well actually how to use the batch-command escape(%) with the /D option.
I read the above items with hope, to be honest they're all a lot more complex than the FOR command option -- If it worked of course.
Using additional forms of for
If command extensions are enabled (that is, the default), the following additional forms of for are supported:
Directories only
If set contains wildcards (* and ?), the specified command executes for each directory (instead of a set of files in a specified directory) that matches set. The syntax is:
for /D {%% | %}variable in (set) do command [CommandLineOptions]
Well, soon we will have to a make a class to simplify things and be typing commands like
dir -options {122b312aa3132-1313-131112f8111111} just so we can do the same as
dir/ad/od