How to programmatically eject locked HDD eSATA drive with PowerShell? - windows

I am trying to eject eSATA HDD drive via "USB tray icon of windows" for safe remove, but it is not possible because "another program using the drive" error (I already tired of this stup## Windows error).
I tried this code on PowerShell:
$vol = get-wmiobject -Class Win32_Volume | where{$_.Name -eq 'F:\'}
$vol.DriveLetter = $null
$vol.Put()
$vol.Dismount($false, $false)
And this other:
$Eject = New-Object -comObject Shell.Application
$Eject.NameSpace(17).ParseName($usbDrvLetter+“:”).InvokeVerb(“Eject”)
And nothing happens.
Any method valid to get this to work?

Not sure but the overload definitions for the .Dismount() method are:
OverloadDefinitions
-------------------
System.Management.ManagementBaseObject Dismount(System.Boolean Force, System.Boolean Permanent)
So maybe change to: $Vol.Dismount($true, $true) effectively force the dismount.
I've tried testing this, except in general I think you should use the CIM cmdlets instead of -WMI. Get-WMIObject is deprecated in Windows PowerShell and has been removed from PowerShell Core.
It's also important to consider the potential return codes (documented here) from the Dismount method.
RETURN VALUE Return code Description
------------ ------------------------
0 Success
1 Access Denied
2 Volume Has Mount Points
3 Volume Does Not Support The No-Autoremount State
4 Force Option Required
To get the volume instance:
$Vol = Get-CimInstance -ClassName Win32_Volume -Filter "DriveLetter = 'E:'"
That's obviously similar enough to the older approach. However, invoking methods is done a little differently:
$Vol | Invoke-CimMethod -MethodName Dismount -Arguments #{ Force = $true; Permanent = $true }
ReturnValue PSComputerName
----------- --------------
2
And, it doesn't appear to dismount the drive. However, if I change the arguments:
$Vol | Invoke-CimMethod -MethodName Dismount -Arguments #{ Force = $true; Permanent = $false }
ReturnValue PSComputerName
----------- --------------
0
This does appear to dismount the drive, as Explorer shifted focus. However, the drive is still visible and when clicked can be accessed. I never saw the Safely eject message. It seems that Dismounting the drive is not equivalent to safely ejecting it.

Related

WMIMethodException with .InstallProductKey

First off, this is my first post, so if I incorrectly posted this in the wrong location, please let me know.
So, what we're trying to accomplish is building a powershell script that we can throw on our workstation image so that once our Windows 10 boxes are done imaging, that we can click on a powershell script, have it pull the key from the BIOS, and automagically activate it. That being said, here is the script that we've put together from various sources.
(Get-WmiObject -query ‘select * from SoftwareLicensingService’).OA3xOriginalProductKey | out-file c:\license.txt
$computer = gc env:computername
$key = get-content c:\license.txt
$service = get-wmiObject -query “select * from SoftwareLicensingService” -computername $computer
$service.InstallProductKey($key) <--------THIS IS WHERE IT FAILS
$service.RefreshLicenseStatus()
We start running into the issues on the line $service.InstallProductKey($key). It seems, that no matter how we try to invoke that, it will consistently fail with the error "Exception calling "InstallProductKey"". I've even replaced the variable ($key) with the specific activation key, and it STILL fails with the same error.
The reason we have it outputting to a license txt file part way through is so that we can verify that the command is indeed pulling the product key (which it is).
At this point, I'm not sure where to go. It seems that people have tried to do this before, however, nobody has really wrapped up their posting with what worked and/or what didn't. I can't imagine that this is impossible, but I'm also not fond of wasting anymore time than needed, so anybody that has any insight into this issue, I'd be very grateful.
We've gotten it to work on two machines that were previously activated, and later deactivated, but on new machines that have been freshly imaged, and have yet to be activated, it will fail every time.
Two things as per my observation:
(Get-WmiObject -query ‘select * from SoftwareLicensingService’).OA3xOriginalProductKey | out-file c:\license.txt
I don't think that it is returning any value to your license.txt.
If yes, then I would like you to see if there is any space before and after the license key. You can use trim during getting the content from the file.
Second thing, when you are getting the content from the file make sure it is not separating into multiple lines. In that case, you have to cast it as string like [String]$key or you can call toString() method for this.
One more important thing is to refresh after the installation.
$service.RefreshLicenseStatus()
Note: Make sure you are running the shell in elevated mode.
Alternative: Try Hardcoding the values and see the result
$key = "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX" # hardcode the key
$computer= "Computer01" # Hardcode the computer
$service = get-wmiObject -query "select * from SoftwareLicensingService" -computername $computer
$service.InstallProductKey($key)
$service.RefreshLicenseStatus()
For further thing ,please post the exact error.
Hope it helps...!!!
Found out that the key from Get-WmiObject has whitespace on the end. The original command will work if a .Trim() is added. Also not running as administrator will result in the same error.
(Get-WmiObject -query ‘select * from SoftwareLicensingService’).OA3xOriginalProductKey | out-file c:\license.txt
$computer = gc env:computername
$key = (get-content c:\license.txt).Trim() #trim here
$service = get-wmiObject -query “select * from SoftwareLicensingService” -computername $computer
$service.InstallProductKey($key)
$service.RefreshLicenseStatus()

Reliably disabling internet access on Windows 7 and up

I'm trying to write a script that requires turning off the Internet entirely and then turning it back on. I'd like it to work in as many cases as possible...
support for Window 7 and up
if multiple Internet connections are on (like WiFi and LAN)
regardless of how these connections are named
limited user account, UAC?
ipconfig /release and ipconfig /renew, as suggested in this answer, do not work with 2 internet connections. /release disables the active connection (say WLAN) but the computer falls back on the LAN connection and you're still online.
$connectedAdapters = Get-WmiObject -Class Win32_NetworkAdapter -Filter "NetConnectionStatus = 2"
$connectedAdapters | Invoke-WmiMethod -Name disable
My questions are:
Is NetConnectionStatus = 2 a reliable proxy for internet access, and is it available on Windows 7 and up, regardless of the brand of your NIC?
Is this compatible with a limited user account with UAC on? I think so...
On my machine this query also catches VirtualBox Host-Only Ethernet Adapter. Is it a problem if I disable/enable it too?
Are Get-WmiObject and Invoke-WmiMethod are available on Windows 7 and up, right?
The questions are not entirely settled, but I've developed a script that works:
PowerShell Internet Connection helper functions: Go-Offline, Go-Online, Test-InternetAccess
#Requires -Version 2.0
function Test-InternetAccess {
<#
.SYNOPSIS
Tests connectivity by pinging Google DNS servers once
.DESCRIPTION
Uses Test-Connection to ping a host, with -Quiet for returning a boolean. The default is a highly available Google DNS server (8.8.4.4)
.EXAMPLE
Test-InternetAccess
.EXAMPLE
Test-InternetAccess example.com
.INPUTS
None.
.OUTPUTS
Boolean
#>
param (
[String]
$RemoteHost = "google-public-dns-b.google.com"
)
Test-Connection -Computer $RemoteHost -BufferSize 16 -Count 1 -Quiet
}
function Go-Offline {
<#
.SYNOPSIS
Disables your internet connection
.DESCRIPTION
Finds all network adapters that appear connected and disables them, taking you offline. Later on you can re-enable just those adapters, because they've been stored in an XML file. Connected adapters are detected through WMI. A NetConnectionStatus value of 2 means Connected. 7 means Media Disconnected.
.EXAMPLE
Go-Offline
.INPUTS
None.
.OUTPUTS
None.
.LINK
https://blogs.technet.microsoft.com/heyscriptingguy/2011/10/07/use-powershell-to-identify-your-real-network-adapter/
.LINK
https://msdn.microsoft.com/en-us/library/aa394216(v=vs.85).aspx
#>
[CmdletBinding(SupportsShouldProcess=$True)]
param()
$XMLLocation = "$env:TEMP\Disabled-NICs.xml"
if (Test-InternetAccess) {
$connectedAdapters = Get-WmiObject -Class Win32_NetworkAdapter -Filter "NetConnectionStatus = 2"
# Go offline
$connectedAdapters | Invoke-WMIMethod -Name disable 1>$null
# Save which adapters were connected at the time
$connectedAdapters | Select Name, DeviceID | Export-Clixml -Path $XMLLocation -Force
Write-Output "You've been taken offline!"
Sleep 1
} else {
Write-Output "Connection already down..."
Sleep 1
}
}
function Go-Online {
<#
.SYNOPSIS
Re-enables your internet connection
.DESCRIPTION
Finds all network adapters that were previously disabled by Go-Offline and enables them. This information is persisted in a temp file.
.EXAMPLE
Go-Online
.INPUTS
None.
.OUTPUTS
None.
.LINK
https://blogs.technet.microsoft.com/heyscriptingguy/2011/10/07/use-powershell-to-identify-your-real-network-adapter/
#>
[CmdletBinding(SupportsShouldProcess=$True)]
param()
$XMLLocation = "$env:TEMP\Disabled-NICs.xml"
if (!(Test-InternetAccess)) {
# Get the NICs that have been previously disabled
$connectedAdapters = Import-Clixml "$env:TEMP\Disabled-NICs.xml" | Select -ExpandProperty DeviceID | ForEach {Get-WMIObject -Class Win32_NetworkAdapter -Filter "DeviceID = $_"}
# Get back online
$connectedAdapters | Invoke-WMIMethod -Name enable | Out-Null
Write-Output "Internet access restored!" # Triggers early, before actual re-connection
Sleep 1
}
}
Tested on Windows 7.

How to change specific registry setting for another user in powershell

Goal:
To edit a specific registry key setting for a specific user, and no others, in powershell.
Known:
OS: Windows 8.1 Embedded Industry Pro (Same as Win 8.1, but with some embedded features)
I can do this manually on the target machine by opening REGEDIT, selecting HKU, then click on File Menu, click on Load Hive, navigate to the user's profile directory, e.g: c:\users\MrEd and when prompted, type in 'ntuser.dat' - import HKEY_CURRENT_USER. The Hive will be loaded into HKU where you can navigate and make necessary modifications.
Summary:
I have a powershell script that returns the SID of the specific user, but when used in context of the registry hive, the hive"is not found" -- so I'm guessing I must be missing a step? How does one "Load Hive" from Powershell? Or am I missing a special, magical, goats-entrails-on-keyboard incantation somewhere?
Param(
[string]$user= $null
)
Function GetSIDfromAcctName()
{
Param(
[Parameter(mandatory=$true)]$userName
)
$myacct = Get-WmiObject Win32_UserAccount -filter "Name='$userName'"
return $myacct.sid
}
if($user)
{
$sid = GetSIDfromAcctName $user
New-PSDrive HKU Registry HKEY_USERS
$myHiveEntry = Get-Item "HKU:\${sid}"
Write-Host "Key:[$myHiveEntry]"
}
Your existing code should work for a user whose hive is already loaded (like a currently logged in user), but it makes no attempt to load the hive.
I don't know of a way to make a programmatic call to load a hive, but you can shell out to reg.exe.
This ends up being kind of janky. It seems to have issues unloading the hive if it's in use anywhere, so I've put a bunch of crap in place in this sample to try to get rid of stuff that might be holding it open, but in my tests, it can take quite a while before the reg unload command is successful, hence the whole retry portion in the finally block.
This is super unpolished, I just whipped it up on the spot.
Function GetSIDfromAcctName()
{
Param(
[Parameter(mandatory=$true)]$userName
)
$myacct = Get-WmiObject Win32_UserAccount -filter "Name='$userName'"
return $myacct.sid
}
$user = 'someuser'
$sid = GetSIDfromAcctName -userName $user
$path = Resolve-Path "$env:USERPROFILE\..\$user\NTUSER.DAT"
try {
reg load "HKU\$sid" $path
#New-PSDrive -Name HKUser -PSProvider Registry -Root "HKEY_USERS\$sid"
#Get-ChildItem HKUser:\
Get-ChildItem Registry::\HKEY_USERS\$sid
} finally {
#Remove-PSDrive -Name HKUser
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
$retryCount = 0
$retryLimit = 20
$retryTime = 1 #seconds
reg unload "HKU\$sid" #> $null
while ($LASTEXITCODE -ne 0 -and $retryCount -lt $retryLimit) {
Write-Verbose "Error unloading 'HKU\$sid', waiting and trying again." -Verbose
Start-Sleep -Seconds $retryTime
$retryCount++
reg unload "HKU\$sid"
}
}
This doesn't use a PS drive, but that code is in there too, commented out.
Note that if you don't name the hive mount point with the SID, you won't actually need the SID at all because you use the username to find the NTUSER.DAT file anyway.

How to find the drive letter of a newly inserted USB drive using Powershell?

I am trying to accomplish this:
When I insert a USB drive in Windows (Win7 and Win8), I want a script to be started (Powershell or batch command) automatically. The machine has autoplay disabled.
I setup a scheduled task with event id 2010 in DriveFrameworks-UserMode as trigger. The scheduled task works. However, I am having difficulties finding the drive letter of the newly inserted USB drive.
I found a few solutions on the Internet, but they didn't fit my requirements. The solutions either check for all drive letters (A to Z), or check for USB drive. I would like to correlate the drive letter that is associated with the event in the DriveFrameworks-UserMode trigger.
This post (How do i get the drive letter of a USB Drive in Powershell?) gave me some hints and I checked classes win32_logicaldisk, win32_diskdrive, and win32_pnpentity, but I couldn't find a clue matching them.
Any help is appreciated.
This should help:
$diskdrive = gwmi win32_diskdrive | ?{$_.interfacetype -eq "USB"}
$letters = $diskdrive | %{gwmi -Query "ASSOCIATORS OF {Win32_DiskDrive.DeviceID=`"$($_.DeviceID.replace('\','\\'))`"} WHERE AssocClass = Win32_DiskDriveToDiskPartition"} | %{gwmi -Query "ASSOCIATORS OF {Win32_DiskPartition.DeviceID=`"$($_.DeviceID)`"} WHERE AssocClass = Win32_LogicalDiskToPartition"} | %{$_. deviceid}
$drive = gwmi win32_volume | ? {$letters -contains ($_.name -replace "\\")}
$drive.DriveLetter
PS. There is a similar discussion here

Is there a way to check AD group membership for a computer?

I am trying to check computer group membership through Powershell. I want to be able to specify a certain computer name and find which groups that computer is in but from a Powershell script. I am planning on running the script on a computer, grabbing the hostname, and then printing out what AD groups that computer is in. Is there an easy way to do this?
EDIT:
So the plan here is to have a computer check to see what groups it is in, then assign a printer based on which group it is in. We have many printers that only 3 to 4 people use but due to the spread out nature of the users cannot downsize the amount of printers. I was looking at group policy but did not want to create 20 different GPOs. I wanted to do this with a logon/startup script. I'm not sure if this is doable or feasible.
Edit #2:
This edit it really late to the party but I figured if anyone found this it could help. We ended up using item level targeting on User>Preferences>Control Panel>Printers objects. We made an account in AD for each group of users needing access to the printers. This worked although it did create a long logon process for the first logon of the computer. We also enabled Point-to-Print restrictions so the drivers were loaded from the servers quietly.
This will give you the group membership (group names) of the local computer (requires powershell 2.0):
([adsisearcher]"(&(objectCategory=computer)(cn=$env:COMPUTERNAME))").FindOne().Properties.memberof -replace '^CN=([^,]+).+$','$1'
Apologies if I'm a bit late to the party on this but I needed to find a computer's group membership as well. After a lot of trial and error, this worked for me.
Get-ADComputer "mycomp" -Properties MemberOf | %{if ($_.MemberOf -like "*group name*") {Write-Host "found"} }
I noticed that if the string comparison is on a separate line, I needed to do the following
$g=Get-ADGroupMember -Identity "CN=myADgroup,OU=myOU,DC=corp,DC=com" -server corp.com
foreach($mbr in $g) {if($name.MemberOf -like "*mycomp*" -eq $true) {Write-Host "found"}}
Not sure but I imagine that testing the computer might be faster and easier than testing the group depending on the number of members.
the gpresult method seems to be the only way I can find that is accurate. Querying AD is not accurate since the computer may have been added to groups but not rebooted. I am sure someone could condense this but it worked well enough for what I needed to see.
# Get all active domain servers
$staledate = (Get-Date).AddDays(-90)
$computers = Get-ADComputer -Filter {(OperatingSystem -Like "*Server*") -and (Enabled -eq $True) -and (LastLogonDate -ge $staledate) -and (Modified -ge $staledate) -and (PasswordLastSet -ge $staledate) -and (whenChanged -ge $staledate) -and (Name -notlike "PHXVSSA101A")} | Select name -expandproperty name
$computers = $computers | Where {(Resolve-DNSName $_.name -ea 0) -and (Test-Connection -ComputerName $_.Name -Count 1 -ea 0)} | Sort
# Loop through all active domain servers
Foreach ($computer in $computers)
{
# Pull the gpresult for the current server
$Lines = gpresult /s $computer /v /SCOPE COMPUTER
# Initialize arrays
$cgroups = #()
$dgroups = #()
# Out equals false by default
$Out = $False
# Define start and end lines for the section we want
$start = "The computer is a part of the following security groups"
$end = "Resultant Set Of Policies for Computer"
# Loop through the gpresult output looking for the computer security group section
ForEach ($Line In $Lines)
{
If ($Line -match $start) {$Out = $True}
If ($Out -eq $True) {$cgroups += $Line}
If ($Line -match $end) {Break}
}
# Loop through all the gathered groups and check for Active Directory groups
ForEach ($group in $cgroups)
{
Try {
$check = Get-ADgroup $group -ErrorAction Stop
If ($check) {
$dgroups += $group
}
}
Catch {}
}
# Output server name and any Active Directory groups it is in
$computer
$dgroups
# End of computers loop
}
try running gpresult /V and check under "security GROUPS"
You can also try gpresult /scope computer /v under a command prompt (elevated to admin) for more specific results
Heres an LDAP query to find if a computer is in a group recursively:
(((objectClass=computer)(sAMAccountName=COMPUTERNAME$))(memberof:1.2.840.113556.1.4.1941:=DistinguishedNameOfGroup))
More info: http://justanotheritblog.co.uk/2016/01/27/recursively-check-if-a-usercomputer-is-a-member-of-an-ad-group-with-powershell-2-0/
To check the computer's own view of group membership, you can run:
(New-Object System.Security.Principal.WindowsPrincipal("$env:computername$")).IsInRole('Example Group')
True
Taking the computer out of Example Group doesn't affect the output of the above until the computer is rebooted.
Try this DOS Command, this will return all the local groups this computer belong to :
net localgroup

Resources