Accessing USB stick without drive letter - powershell

I am using powershell 2.0 in windows 7.
I would like to copy a file from my USB stick to a directory on my main hard drive using cmd or powershell. However, I need this to function on any PC without any input of the USB's current drive letter. In case that didn't make sense, let me rephrase it. I need a powershell or cmd command/ batch script to copy a file from my USB stick to my hard drive without any input.
Ideal command would assign the variable mydrive to the drive letter and allow me to run something like this in cmd
copy myvar:/path/fileToCopy.txt/ C:/path/of/target/directory/
I would really appreciate if I could use just my USB sticks name ('DD') to copy like this:
copy DD:/path/fileToCopy.txt/ C:/path/of/target/directory/
I've done well over an hours worth of research trying to find a way to pull this off and can't. Any help is greatly appreciated. Especially if it is clear how to use it. I am very new to powershell and cmd commands and don't understand the syntax. So stuff like [put drive name here] to show me how to use it would be amazing and is where a lot of forums are missing out.

You can do this like below:
$destination = 'C:\path\of\target\directory'
$sourceFile = 'path\fileToCopy.txt' # the path to the file without drive letter
# get (an array of) USB disk drives currently connected to the pc
$wmiQuery1 = 'ASSOCIATORS OF {{Win32_DiskDrive.DeviceID="{0}"}} WHERE AssocClass = Win32_DiskDriveToDiskPartition'
$wmiQuery2 = 'ASSOCIATORS OF {{Win32_DiskPartition.DeviceID="{0}"}} WHERE AssocClass = Win32_LogicalDiskToPartition'
$usb = Get-WmiObject Win32_Diskdrive | Where-Object { $_.InterfaceType -eq 'USB' } |
ForEach-Object {
Get-WmiObject -Query ($wmiQuery1 -f $_.DeviceID.Replace('\','\\')) #'# double-up the backslash(es)
} |
ForEach-Object {
Get-WmiObject -Query ($wmiQuery2 -f $_.DeviceID)
}
# loop through these disk(s) and test if the file to copy is on it
$usb | ForEach-Object {
# join the DeviceID (like 'H:') with the file path you need to copy
$file = Join-Path -Path $_.DeviceID -ChildPath $sourceFile
if (Test-Path -Path $file -PathType Leaf) {
Copy-Item -Path $file -Destination $destination
break # exit the loop because you're done
}
}
Hope that helps
If you upgrade your version of PowerShell, you can replace the Get-WmiObject with Get-CimInstance for better performance. See this and that

Related

Get-ChildItem on Multiple Computers, Performance Issues

I'm wanting to improve on my script to be able to accomplish the following:
Scan servers based on get-adcomputer on specific OUs.
Scan each server based on whatever drive letter it has.
Scan each server for log4j.
Export all results to a CSV that identifies the folder path, name of file, and the server that the file was found on.
I have been using the following code to start with:
$Servers = Get-ADComputer -Filter * -SearchBase "OU=..." | Select -ExpandProperty Name
foreach ($server in $Servers){
Invoke-Command -ComputerName $Server -ScriptBlock {
$Drives = (Get-PSDrive -PSProvider FileSystem).Root
foreach ($drive in $Drives){
Get-ChildItem -Path $drive -Force -Filter *log4j* -ErrorAction SilentlyContinue | '
foreach{
$Item = $_
$Type = $_.Extension
$Path = $_.FullName
$Folder = $_.PSIsContainer
$Age = $_.CreationTime
$Path | Select-Object `
#{n="Name";e={$Item}}, `
#{n="Created";e={$Age}},`
#{n="FilePath";e={$Path}},`
#{n="Extension";e={if($Folder){"Folder"}else{$Type}}}`
} | Export-Csv C:\Results.csv -NoType
}
}
I am having the following issues and would like to address them to learn.
How would I be able to get the CSV to appear the way I want, but have it collect the information and store it on my machine instead of having it on each local server?
I have noticed extreme performance issues on the remote hosts when running this. WinRM takes 100% of the processor while it is running. I have tried -Include first, then -Filter, but to no avail. How can this be improved so that at worst, it's solely my workstation that's eating the performance hit?
What exactly do the ` marks do?
I agree with #SantiagoSquarzon - that's going to be a performance hit.
Consider using writing a function to run Get-ChildItem recursively with the -MaxDepth parameter, including a Start-Sleep command to pause occasionally. Also, you may want to note this link
You'd also want to Export-CSV to a shared network drive to collect all the machines' results.
The backticks indicate a continuation of the line, like \ in bash.
Finally, consider using a Scheduled Task or start a powershell sub-process with a lowered process priority, maybe that will help?

Create PS script to find files

I want to start by saying coding is a bit outside of my skill set but because a certain problem keeps appearing at work, I'm trying to automate a solution.
I use the below script to read an input file for a list of name, search the C:\ for those files, then write the path to an output file if any are found.
foreach($line in Get-Content C:\temp\InPutfile.txt) {
if($line -match $regex){
gci -Path "C:\" -recurse -Filter $line -ErrorAction SilentlyContinue |
Out-File -Append c:\temp\ResultsFindFile.txt
}
}
I would like to make two modifications to this. First, to search all drives connected to the computer not just C:\. Next, be able to delete any found files. I'm using the Remove-Item -confirm command but so far can't make it delete the file it just found.

Delete directory regardless of 260 char limit

I'm writing a simple script to delete USMT migration folders after a certain amount of days:
## Server List ##
$servers = "Delorean","Adelaide","Brisbane","Melbourne","Newcastle","Perth"
## Number of days (-3 is over three days ago) ##
$days = -3
$timelimit = (Get-Date).AddDays($days)
foreach ($server in $servers)
{
$deletedusers = #()
$folders = Get-ChildItem \\$server\USMT$ | where {$_.psiscontainer}
write-host "Checking server : " $server
foreach ($folder in $folders)
{
If ($folder.LastWriteTime -lt $timelimit -And $folder -ne $null)
{
$deletedusers += $folder
Remove-Item -recurse -force $folder.fullname
}
}
write-host "Users deleted : " $deletedusers
write-host
}
However I keep hitting the dreaded Remove-Item : The specified path, file name, or both are too long. The fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters.
I've been looking at workarounds and alternatives but they all revolve around me caring what is in the folder.
I was hoping for a more simple solution as I don't really care about the folder contents if it is marked for deletion.
Is there any native Powershell cmdlet other than Remove-Item -recurse that can accomplish what I'm after?
I often have this issue with node projects. They nest their dependencies and once git cloned, it's difficult to delete them. A nice node utility I came across is rimraf.
npm install rimraf -g
rimraf <dir>
Just as CADII said in another answer: Robocopy is able to create paths longer than the limit of 260 characters. Robocopy is also able to delete such paths. You can just mirror some empty folder over your path containing too long names in case you want to delete it.
For example:
robocopy C:\temp\some_empty_dir E:\temp\dir_containing_very_deep_structures /MIR
Here's the Robocopy reference to know the parameters and various options.
I've created a PowerShell function that is able to delete a long path (>260) using the mentioned robocopy technique:
function Remove-PathToLongDirectory
{
Param(
[string]$directory
)
# create a temporary (empty) directory
$parent = [System.IO.Path]::GetTempPath()
[string] $name = [System.Guid]::NewGuid()
$tempDirectory = New-Item -ItemType Directory -Path (Join-Path $parent $name)
robocopy /MIR $tempDirectory.FullName $directory | out-null
Remove-Item $directory -Force | out-null
Remove-Item $tempDirectory -Force | out-null
}
Usage example:
Remove-PathToLongDirectory c:\yourlongPath
This answer on SuperUser solved it for me: https://superuser.com/a/274224/85532
Cmd /C "rmdir /S /Q $myDir"
I learnt a trick a while ago that often works to get around long file path issues. Apparently when using some Windows API's certain functions will flow through legacy code that can't handle long file names. However if you format your paths in a particular way, the legacy code is avoided. The trick that solves this problem is to reference paths using the "\\?\" prefix. It should be noted that not all API's support this but in this particular case it worked for me, see my example below:
The following example fails:
PS D:\> get-childitem -path "D:\System Volume Information\dfsr" -hidden
Directory: D:\System Volume Information\dfsr
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a-hs 10/09/2014 11:10 PM 834424 FileIDTable_2
-a-hs 10/09/2014 8:43 PM 3211264 SimilarityTable_2
PS D:\> Remove-Item -Path "D:\System Volume Information\dfsr" -recurse -force
Remove-Item : The specified path, file name, or both are too long. The fully qualified file name must be less than 260
characters, and the directory name must be less than 248 characters.
At line:1 char:1
+ Remove-Item -Path "D:\System Volume Information\dfsr" -recurse -force
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : WriteError: (D:\System Volume Information\dfsr:String) [Remove-Item], PathTooLongExcepti
on
+ FullyQualifiedErrorId : RemoveItemIOError,Microsoft.PowerShell.Commands.RemoveItemCommand
PS D:\>
However, prefixing the path with "\\?\" makes the command work successfully:
PS D:\> Remove-Item -Path "\\?\D:\System Volume Information\dfsr" -recurse -force
PS D:\> get-childitem -path "D:\System Volume Information\dfsr" -hidden
PS D:\>
If you have ruby installed, you can use Fileman:
gem install fileman
Once installed, you can simply run the following in your command prompt:
fm rm your_folder_path
This problem is a real pain in the neck when you're developing in node.js on Windows, so fileman becomes really handy to delete all the garbage once in a while
This is a known limitation of PowerShell. The work around is to use dir cmd (sorry, but this is true).
http://asysadmin.tumblr.com/post/17654309496/powershell-path-length-limitation
or as mentioned by AaronH answer use \?\ syntax is in this example to delete build
dir -Include build -Depth 1 | Remove-Item -Recurse -Path "\\?\$($_.FullName)"
If all you're doing is deleting the files, I use a function to shorten the names, then I delete.
function ConvertTo-ShortNames{
param ([string]$folder)
$name = 1
$items = Get-ChildItem -path $folder
foreach ($item in $items){
Rename-Item -Path $item.FullName -NewName "$name"
if ($item.PSIsContainer){
$parts = $item.FullName.Split("\")
$folderPath = $parts[0]
for ($i = 1; $i -lt $parts.Count - 1; $i++){
$folderPath = $folderPath + "\" + $parts[$i]
}
$folderPath = $folderPath + "\$name"
ConvertTo-ShortNames $folderPath
}
$name++
}
}
I know this is an old question, but I thought I would put this here in case somebody needed it.
There is one workaround that uses Experimental.IO from Base Class Libraries project. You can find it over on poshcode, or download from author's blog. 260 limitation is derived from .NET, so it's either this, or using tools that do not depend on .NET (like cmd /c dir, as #Bill suggested).
Combination of tools can work best, try doing a dir /x to get the 8.3 file name instead. You could then parse out that output to a text file then build a powershell script to delete the paths that you out-file'd. Take you all of a minute. Alternatively you could just rename the 8.3 file name to something shorter then delete.
For my Robocopy worked in 1, 2 and 3
First create an empty directory lets say c:\emptydir
ROBOCOPY c:\emptydir c:\directorytodelete /purge
rmdir c:\directorytodelete
This is getting old but I recently had to work around it again. I ended up using 'subst' as it didn't require any other modules or functions be available on the PC this was running from. A little more portable.
Basically find a spare drive letter, 'subst' the long path to that letter, then use that as the base for GCI.
Only limitation is that the $_.fullname and other properties will report the drive letter as the root path.
Seems to work ok:
$location = \\path\to\long\
$driveLetter = ls function:[d-z]: -n | ?{ !(test-path $_) } | random
subst $driveLetter $location
sleep 1
Push-Location $driveLetter -ErrorAction SilentlyContinue
Get-ChildItem -Recurse
subst $driveLetter /D
That command is obviously not to delete files but can be substituted.
PowerShell can easily be used with AlphaFS.dll to do actual file I/O stuff
without the PATH TOO LONG hassle.
For example:
Import-Module <path-to-AlphaFS.dll>
[Alphaleonis.Win32.Filesystem.Directory]::Delete($path, $True)
Please see at Codeplex: https://alphafs.codeplex.com/ for this .NET project.
I had the same issue while trying to delete folders on a remote machine.
Nothing helped but... I found one trick :
# 1:let's create an empty folder
md ".\Empty" -erroraction silentlycontinue
# 2: let's MIR to the folder to delete : this will empty the folder completely.
robocopy ".\Empty" $foldertodelete /MIR /LOG+:$logname
# 3: let's delete the empty folder now:
remove-item $foldertodelete -force
# 4: we can delete now the empty folder
remove-item ".\Empty" -force
Works like a charm on local or remote folders (using UNC path)
Adding to Daniel Lee's solution,
When the $myDir has spaces in the middle it gives FILE NOT FOUND errors considering set of files splitted from space. To overcome this use quotations around the variable and put powershell escape character to skip the quatations.
PS>cmd.exe /C "rmdir /s /q <grave-accent>"$myDir<grave-accent>""
Please substitute the proper grave-accent character instead of <grave-accent>
SO plays with me and I can't add it :). Hope some one will update it for others to understand easily
Just for completeness, I have come across this a few more times and have used a combination of both 'subst' and 'New-PSDrive' to work around it in various situations.
Not exactly a solution, but if anyone is looking for alternatives this might help.
Subst seems very sensitive to which type of program you are using to access the files, sometimes it works and sometimes it doesn't, seems to be the same with New-PSDrive.
Any thing developed using .NET out of the box will fail with paths too long. You will have to move them to 8.3 names, PInVoke (Win32) calls, or use robocopy

How to find my USB flash drive's path with PowerShell

I am preparing a ps1 file. It will install some programmes silently. But my programmes' setup files were saved in my USB flash drive. In my ps1 file,
cd E:\User\User_Setups
This path is my USB flash drive's path. But it will change on the other machine. Maybe G:\, F:\ etc.
Naturally, I don't want to change this path for every different machines. How PowerShell find my USB flash drive's path by a command-line?
I added a VolumeLabel("MyToolBox") to my usb Stick and put following line in the profile.ps1:
Get-DriveInfo | % { if( $_.VolumeLabel -eq "MyToolBox"){ Set-Location $_.Name; ./Startup.ps1}}
Get-DriveInfo comes from the module Pscx: http://pscx.codeplex.com/
You need to import this in you profile too...
The Startup.ps1 script is in the root of my usb stick and registers aliases on the stick for use in the session...
I've done this with WMI: using the device type to get to the drive letter. in simplified form (real script has logging and error handling). I initially obtained $deviceCaption from Win32_PnpEntity and Device Manager:
$objs = #(Get-WmiObject -Query "select Caption,__RELPATH from Win32_PnpEntity where caption=""$deviceCaption""")
if ($objs.Length -eq 0) {
throw "MP3 Player is not connected"
} elseif ($objs.Length -gt 1) {
throw "Seem to be multiple MP3 players connected"
}
$relPath = $objs[0];
$objs = #(Get-WmiObject -Query "ASSOCIATORS OF {$relPath} where resultclass=Win32_DiskDrive")
$relPath = $objs[0].__RelPath;
$objs = #(Get-WmiObject -Query "ASSOCIATORS OF {$relPath} where resultclass=Win32_DiskPartition")
$relPath = $objs[0].__RelPath;
$objs = #(Get-WmiObject -Query "ASSOCIATORS OF {$relPath} where resultclass=Win32_LogicalDisk")
$relPath = $objs[0].__RelPath;
Write-Debug "RelPath #4: $($objs[0].__RelPath), drive: $($objs[0].DeviceID)"
$objs[0].DeviceID
That final expression returns the drive name, something like: Q: (it does include to colon).
Note this assumes the device has a single disk with a single partition.
There are probably several good ways to do it. Personally, I'd put an id file disk.id on drive and just search each drive programmatically until I found the id file with the id I'm looking for. Something like this:
#Start i at 65 to represent A
i=65
do {
$idFile = Get-Content [char]$i:\disk.id -totalcount 1
if( $idFile -eq "MyIdDrive" ) { #whatever your first line in the id file may be
Write-Host "[char]$i is my drive!"
}
$i++
}
while ($i -le 65+26)
It's a brute force method and you may need to error handle the Get-Content but it should work on most Windows installs. The only case where you'd run into problems is with double case drive names and then you'd just need to create a more sophisticated loop.
If Powershell is more powerful than regular windows CMD.exe then why do I only have to use the command
ECHO %~dp0
in CMD.exe to answer your question? Seems to me you have to write a lot of extra code to get the relative path of the batch or cmd file information, and this comes up often in batch scripts. Powershell fail.
http://ss64.com/nt/syntax-args.html for more info.
You can get the current file, and use this to get the current usb drive.
$currentDirectory = $myInvocation.MyCommand.ScriptBlock.File | Split-Path | Get-Item
$currentDirectory.PSDrive.Root

Powershell script to locate specific file/file name?

I wanted to write a small script that searched for an exact file name, not a string within a file name.
For instance if I search for 'hosts' using Explorer, I get multiple results by default. With the script I want ONLY the name I specify. I'm assuming that it's possible?
I had only really started the script and it's only for my personal use so it's not important, it's just bugging me. I have several drives so I started with 2 inputs, one to query drive letter and another to specify file name. I can search by extension, file size etc but can't seem to pin the search down to an exact name.
Any help would be appreciated!
EDIT : Thanks to all responses. Just to update. I added one of the answers to my tiny script and it works well. All three responses worked but I could only use one ultimately, no offence to the other two. Cheers. Just to clarify, 'npp' is an alias for Notepad++ to open the file once found.
$drv = read-host "Choose drive"
$fname = read-host "File name"
$req = dir -Path $drv -r | Where-Object { !$PsIsContainer -and [System.IO.Path]::GetFileNameWithoutExtension($_.Name) -eq $fname }
set-location $req.directory
npp $req
From a powershell prompt, use the gci cmdlet (alias for Get-ChildItem) and -filter option:
gci -recurse -filter "hosts"
This will return an exact match to filename "hosts".
SteveMustafa points out with current versions of powershell you can use the -File switch to give the following to recursively search for only files named "hosts" (and not directories or other miscellaneous file-system entities):
gci -recurse -filter "hosts" -File
The commands may print many red error messages like "Access to the path 'C:\Windows\Prefetch' is denied.".
If you want to avoid the error messages then set the -ErrorAction to be silent.
gci -recurse -filter "hosts" -File -ErrorAction SilentlyContinue
An additional helper is that you can set the root to search from using -Path.
The resulting command to search explicitly search from, for example, the root of the C drive would be
gci -Recurse -Filter "hosts" -File -ErrorAction SilentlyContinue -Path "C:\"
Assuming you have a Z: drive mapped:
Get-ChildItem -Path "Z:" -Recurse | Where-Object { !$PsIsContainer -and [System.IO.Path]::GetFileNameWithoutExtension($_.Name) -eq "hosts" }
I use this form for just this sort of thing:
gci . hosts -r | ? {!$_.PSIsContainer}
. maps to positional parameter Path and "hosts" maps to positional parameter Filter. I highly recommend using Filter over Include if the provider supports filtering (and the filesystem provider does). It is a good bit faster than Include.
I'm using this function based on #Murph answer.
It searches inside the current directory and lists the full path:
function findit
{
$filename = $args[0];
gci -recurse -filter "*${filename}*" -file -ErrorAction SilentlyContinue | foreach-object {
$place_path = $_.directory
echo "${place_path}\${_}"
}
}
Example usage: findit myfile
To search the whole computer:
gdr -PSProvider 'FileSystem' | %{ ls -r $_.root} 2>$null | where { $_.name -eq "httpd.exe" }
In findFileByFilename.ps1 I have:
# https://stackoverflow.com/questions/3428044/powershell-script-to-locate-specific-file-file-name
$filename = Read-Host 'What is the filename to find?'
gci . -recurse -filter $filename -file -ErrorAction SilentlyContinue
# tested works from pwd recursively.
This works great for me. I understand it.
I put it in a folder on my PATH.
I invoke it with:
> findFileByFilename.ps1
To search the whole computer:
gdr -PSProvider 'FileSystem' | %{ ls -r $.root} 2>$null | where {
$.name -eq "httpd.exe" }
I am pretty sure this is a much less efficient command, for MANY reasons, but the simplest is your piping everything to your where-object command, when you could still use -filter "httpd.exe" and save a ton of cycles.
Also, on a lot of computers the get-psdrive is gonna grab shared drives, and I am pretty sure you wanted that to get a complete search. Most shares can be IMMENSE with regard to the sheer number of files and folders, so at the very least I would sort my drives by size, and add a check after each search to exit the loop if we locate the file. That is if you are looking for a single instance, if not the only way to save yourself the IMMENSE time sink of searching a 10TB share or two, is to comment the command and highly suggest any user who were to need to use it should limit their search as much as they can. For instance our User Profile share is 10TB, at least the one I am on is, and I can limit my search to the directory $sharedrive\users\myname and search my 116GB directory rather than the 10TB one. Too many unknowns with shares for this type of script, which is already super inefficient with regard to resources and speed.
If I was seriously considering using something like this, I would add a call to a 3rd party package and leverage a DB.