How to find my USB flash drive's path with PowerShell - 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

Related

Pulling Win10 activation codes from remote computers

I'm brand new to PS scripting, so bear with me :)
I'm trying to create a PS script that will write the Win10 activation code to a file then copy that file to a central repo to then manually activate.
I'm creating a PS script and trying to run
cscript.exe c:\windows\system32\slmgr.vbs -dti >
$SourceDir\$env:computername.txt
$SourceDir = \\computer01\c$\temp
I need to run it from one computer, remotely connecting to every computer on the network, creating the computername.txt file then copying that file back to a central repository for all the files.
What I have so far:
$s1=New-PSSession -ComputerName computer01 -Credential $AdminCred
Test-Connection -ComputerName computer01
$id='\\computer01\windows\system32'
$SourceDir='\\computer01\c$\temp'
md $SourceDir
$GetActID=cscript.exe $id\slmgr.vbs -dti >
$SourceDir\$env:computername.txt
Invoke-Command -Session $s1 -ScriptBlock { $Using:GetActID }
Then I call a batch file that copies the computername.txt file from the computer01 over to a repository where they are going to sit.
I FINALLY got it working correctly except for the name of the file isn't naming it to the computer01, it's naming it with the hostname of the computer I'm running it from, therefore the filenames are identical. I had the naming piece working, but I had to change the way I was remoting into the computer and now it's not naming correctly.
Any idea on how I could get it to name the file to be related to the remote computer?
**I'm still working on the whole piece of the puzzle where it goes back to an excel sheet pulled from AD and pulls the host names from that sheet to connect to each machine, I believe I'll be adding a ForEach syntax in there somehow for that.
Although not sure how you are getting the list of "every computer on the network", chances are you are doing this using
# get a list of all AD computers (their names only)
$computers = (Get-ADComputer -Filter *).Name
Then I think you don't need to have every computer save the file on its own disk and later copy these files to a central share.
Instead, just capture the info in a variable and after the loop write the file to the central share as structured CSV file combining all computernames and install id's so you can open in Excel.
Using the array of computernames from above, iterate through them
$result = $computers | ForEach-Object {
# test if the computer can be reached
if (Test-Connection -ComputerName $_ -Count 1 -Quiet) {
$installId = Invoke-Command -ComputerName $_ -ScriptBlock {
cscript.exe //nologo "$env:SystemRoot\System32\slmgr.vbs" -dti
}
# $installId is returned as array !
# output an object with two properties
[PsCustomObject]#{
Computer = $_
InstallId = $installId[0] -replace '\D' # remove everything non-numeric
}
}
else {
Write-Warning "Computer $_ is not responding"
}
}
# now you can display the result on screen
$result | Format-Table -AutoSize
# or by means of the GridView if you prefer
$result | Out-GridView -Title 'Computer InstallIds'
# and save the results in your central share as structured CSV file
$result | Export-Csv -Path '\\server\share\restofpath\ComputerInstallIds.csv' -NoTypeInformation
You may have to append -Credential $adminCreds to the Invoke-Command call to make sure you have permissions to have each machine run that piece of code in the scriptblock. The easiest way of obtaining that credential is to start off with $adminCreds = Get-Credential -Message "Please enter administrator credentials"

How do you delete user profiles in powershell?

I am writing a powershell script to delete user profiles and I understand the method I am using is not the best. I was wondering what would be a better way to do it? I am still very much new to powershell but I am willing to learn
The code I already have:
$ErrorActionPreference= 'silentlycontinue'
$Users = Get-WmiObject -Class Win32_UserProfile
$IgnoreList = "helpdesk", "administrator", "Default"
:OuterLoop
foreach ($User in $Users) {
foreach ($name in $IgnoreList) {
if ($User.localpath -like "*\$name") {
continue OuterLoop
}
}
$User.Delete()
}
WHy script this when the enterprise approach is GPO.
How to Delete Old User Profiles Using GPO and PowerShell?
No reason to do this from scratch, leverage what others have provided...
Use PowerShell to remove local profiles
How to delete user profiles older than a specified number of days in Windows
...as well as the modules from the MS powershellgallery.com, as you look at whatever approach you decide use.
Find-Module -Name '*user*profile*' | Format-Table -AutoSize
<#
# Results
Version Name Repository Description
------- ---- ---------- -----------
1.0 UserProfile PSGallery This module manages user profiles on local and remote computers
0.1.1 Microsoft.Graph.Users.ProfilePhoto PSGallery Microsoft Graph PowerShell Cmdlets
1.0.6 Get-UserProfile PSGallery The Get-UserProfile module list or remove User Profiles from local
#>
Find-Module -Name '*userprofile*' | Format-List -Force
Update
Yet, you specifically said...
'I understand the method I am using is not the best. I was wondering
what would be a better way to do it?
... and what we all have suggested, using GPO is the best way, the normal industry-accepted enterprise way to do this. Don't script, unless you have no other choice. Windows AD will do this for you.
Don't reinvent the wheel unless you know it's really a better wheel. In learning, of course, there is study, trial, and error, but learn and use from sources that have already done this. There are tons of examples all over the web for this use case. Just search for it. No reason to do this from scratch.
'powershell remove user profiles'
Which are showing what you are already doing... Example(s) - pre-built scripts for this use case via the Ms powershellgallery.com.
Use PowerShell delete a user profile (step-by-step guide)
Get-CimInstance -ComputerName SRV1,SRV2,SRV3 -Class Win32_UserProfile |
Where-Object { $_.LocalPath.split('\')[-1] -eq 'UserA' } |
Remove-CimInstance
Remove-UserProfile - Remove Local User Profiles and Clean C:\Users Directory
This script contains a function (Remove-UserProfile) which is used to
remove user profiles, and additional contents of the C:\Users
directory (if specified) on a local computer.
Download: Remove-UserProfile.ps1
Delete Unused user Profiles on local machine (PowerShell)
Script Delete user profiles over multiple servers v2
ANd using the modules that you see from the above commands, is not something to do later in life. Those are in / available from MS and in PowerShell directly for a reason. Everything you are using in PowerShell is coming from modules hosted on your machine and the ones you download and install from MS and other resources.
Again, use the built-in enterprise tools in Windows or other chosen OS as designed, and if they don't provide what you need, then look to other options, like scripting to an object-level that the enterprise tool is not exposing in its GUI.
I do a similar thing. With a lot of profiles, I've found I've had to wait for the cpu to calm down because of the appxsvc service spawning threads without limit. I used to delete per-user firewall rules, but that doesn't seem necessary anymore.
$excludedprofilelist = 'C:\Users\admin1','C:\users\admin2'
$myprofiles = $profiles | where { !$_.Special -and
$excludedprofilelist -notcontains $_.LocalPath }
$sleepseconds = 1
$numcores = 4
foreach ($profile in $myprofiles) {
$msg = "deleting profile " + $profile.LocalPath
$profile | remove-wmiobject
$msg = $msg + " $?"
echo $msg # the result
# recycle bin
if (test-path c:\`$recycle.bin\$($profile.sid)) {
rm -r c:\`$recycle.bin\$($profile.sid) -force
}
# wait for appx cleanup, what if profile delete error?
#while ( (get-appxpackage -user $profile.sid).count ) {
# sleep 1
#}
do {
# make sure it's running
$id = Get-WmiObject -Class Win32_Service -Filter "Name = 'appxsvc'" |
Select-Object -ExpandProperty ProcessId
# $proc = get-process -id $id
# access is denied
# 4proc.priorityclass = 'belownormal'
$cpu1 = (get-process -Id $id).cpu
sleep $sleepseconds
$cpu2 = (get-process -Id $id).cpu
$cpu = [int](($cpu2 - $cpu1)/($numcores*$sleepseconds) * 100)
} while ($cpu)
}

Accessing USB stick without drive letter

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

Printer Migration - Powershell script

I have found some great examples on foreach loops in Powershell here but I just can't wrap my head around foreach loops for what I am doing.
I found great scripts that deal with migrating printer when migrating from one Windows print server to another however my challenge is that I am migrating from an Novell iPrint server to a Windows server.
The struggle is that the printer name or share name (or any printer property) for iPrint printer is not the hostname so I have to come up with some translation table with iPrint name and Printer hostname.
Initially, I wanted to just have column 2 of my translation table have it execute my powershell command to install a network printer which would make things easier.
I am in the process of trying to create a logon script to query printers that are installed on computer and have it do a 'foreach' loop against a CSV with iPrint names and hostnames.
csv 1
installediprintprintername1
installediprintprintername2
installediprintprintername3
printtranslationtable.csv
column 1 column 2
iprintprintername1 hostnameprinter1
iprintprintername2 hostnameprinter2
iprintprintername3 hostnameprinter3
iprintprintername4 hostnameprinter4
This is what I got so far but not able to get it to work. Any help would be appreciated!
$printers = #(Get-wmiobject win32_printer)
$path = "\\networkdrive\printtranslationtable.csv"
$printertranslation = Import-Csv -path $path
foreach ($iprintprinter in $printtranslationtable) {
foreach ($name in $csv1) {
if ($name -eq $printtranslationtable.column1) {
Write-Host $newPrinter = $printtranslationtable.column2
}
}
}
Update
So I was able to tweak the script #TheMadTechnician suggested and able to get this PS script to work in my environment. What I am trying to do is to check if new printers are installed and if they are then just exit script. This is what I have but can't get it to exit or break. I was also trying to write the new printers into text file but not necessary, I would like for it to stop executing script.
if (($printers.name -like "\winprint*") -eq $true) {
$printers.name -like "\winprint\" | out-file -FilePath "C:\windowsprinters.txt" -Append
{break} {exit}
}
When you read the file with Import-Csv, PowerShell creates an array of custom objects with property names from the header line. On the other hand Get-Content produces simple array of string values. I came up with this one liner, which goes thru the translation table and checks if the printer list contains one. This is not optimal if you have billions of printers, but keeps things clear:
printers.txt:
iprinter2
iprinter3
printertable.csv:
"Column1";"Column2"
"iprinter1";"hostname1"
"iprinter2";"hostname2"
"iprinter3";"hostname3"
"iprinter4";"hostname4"
PowerShell:
$printers = Get-Content .\printers.txt
$prtable = Import-Csv -Delimiter ";" .\printertable.csv
$prtable | ?{ $printers -contains $_.Column1 } | %{Write-Host "Install $($_.Column2)"}
Ok, so you query what printers are installed, and you have a translation table loaded from a CSV, now you just need to look at that translation table and cross reference which entries have a listing in the local computer's printer listings.
$printers = #(Get-wmiobject win32_printer)
$path = "\\networkdrive\printtranslationtable.csv"
$printertranslation = Import-Csv -path $path
$printertranslation | Where{$_.Column1 -in $printers.ShareName} | ForEach{ Add-Printer $_.Column2 }
I don't know what property of the win32_printer object aligns best for you, but I would suggest ShareName or DeviceId. Those should be something like:
ShareName: XeroxColor02
DeviceId: \\printserver\XeroxColor02

Open command prompt to access folders of a USB connected windows phone

I am trying to open a command prompt to access folders of a USB connected windows phone. I have tried several commands like the following but to no avail.
wmic logicaldisk get name
GET-WMIOBJECT win32_diskdrive | Where { $_.InterfaceType -eq 'USB' }
Could someone suggest me the best way to accomplish this without using any tool?
My task is to access the mobile device to adjust language settings using PowerShell commands.
Phone : Lumia 1020 running Windows Phone 8.
To get a list of USB drives attached to the PC, execute this command.
Get-WmiObject Win32_Volume -Filter "DriveType='2'"
If your mobile is attached as a USB disk, it should show up. From the data you get back, you should be able to extract things like Caption, Label, Name and DriveLetter. Then you can automate things a little bit further:
cd (Get-WmiObject Win32_Volume -Filter "DriveType='2'" | Where-Object label -eq "YourDiskName").DriveLetter
EDIT: Since Get-WmiObject command is now depreciated, the preferred way is now to use Get-CimInstance.
Get-CimInstance -Query "SELECT * FROM Win32_LogicalDisk WHERE DriveType=2"
You can get the phone's top-level directory using this function, then add the root folder name from explorer e.g. 'Internal Shared Storage', 'Card', etc.
function Get-PhoneMainDir($phoneName) {
$o = New-Object -com Shell.Application
$rootComputerDirectory = $o.NameSpace(0x11)
$phoneDirectory = $rootComputerDirectory.Items() | Where-Object {
$_.Name -eq $phoneName } | select -First 1
if ($phoneDirectory -eq $null) {
throw "Not found '$phoneName' folder in This computer. Connect your phone."
}
return $phoneDirectory;
}
$phoneName is the name of the phone in Explorer, e.g. 'Pixel 5a'
A full example is available here:
https://github.com/nosalan/powershell-mtp-file-transfer/blob/master/phone_backup_recursive.ps1