I'm using PowerShell for the first time to check if a unc path is already mapped, and if it is delete it to create it again under specific credentials.
The problem I have found is if the path exists in the net use list but has a drive letter, you have to delete it by the drive letter which can be of course random. So I need to find out the drive letter when I match it but I am clueless as to how.
$userPass = '/user:Domain\User password';
$ltr = ls function:[d-z]: -n | ?{ !(test-path $_) } | random;
$share = '\\\\server\\folder';
$newMap = 'net use '+ $ltr + ' '$share + ' '$userPass;
foreach ($con in net use) {
if ($con -match $share) {
$MappedLetter = /*something here to find the matched drive?*/
if ($MappedLetter) {
net use $MappedLetter /delete
} else {
net use $share /delete
}
}
};
net use $newMap;
[Edit]
I have tried the Get-PSDrive but this only works IF the UNC path is mapped to a drive. As these can at times be a "zombie", i.e. exist in net use but have no letter, this method won't always work. I can combine the two methods (above an Get-PSDrive) but if anyone has a cleaner way please let me know!
You should be able to check whether a particular share is already mapped via Get-PSDrive:
$share = '\\server\share'
$drive = Get-PSDrive |
Where-Object { $_.DisplayRoot -eq $share } |
Select-Object -Expand Name
if ($drive) {
"${share} is already mapped to ${drive}."
}
If you just want to remove the drive in case it is mapped you could do something like this:
Get-PSDrive | Where-Object {
$_.DisplayRoot -eq $share
} | Remove-PSDrive
Update:
AFAIK PowerShell can't enumerate and remove disconnected drives directly. You can obtain that information from the registry:
$share = '\\server\share'
$drive = Get-ItemProperty 'HKCU:Network\*' |
Where-Object { $_.RemotePath -eq $share } |
Select-Object -Expand PSChildName
or parse it out of the net use output:
net use | Where-Object {
$_ -match '(?<=^Unavailable\s+)\w:'
} | ForEach-Object {
$matches[0]
}
However, from what I know you still won't be able to remove the drive (e.g. via net use) as long as it remains disconnected. For removing a disconnected drive (e.g. when a share has become permanently unavailable) that you'd need to remove the mapping information from the registry:
$share = '\\server\share'
Get-ItemProperty 'HKCU:Network\*' |
Where-Object { $_.RemotePath -eq $share } |
Remove-Item
# also remove mount point in Windows Explorer
$key = 'Software\Microsoft\Windows\CurrentVersion\Explorer\MountPoints2'
$subkey = Join-Path $key ($share -replace '\\', '#')
Get-ItemProperty "HKCU:${subkey}" | Remove-Item
Related
I am working on a PowerShell command to search across drives for a specific file. I am new to PowerShell so most of what I have already is just stuff I found online. At the moment I have this:
$ExclDrives = ('C')
>> Get-PSDrive -PSProvider FileSystem | Where-Object {$_.Name -notin $ExclDrives} `
>> | % {write-host -f Green "Searching " $_.Root;get-childitem $_.Root -include *MyFile.txt -r `
>> | sort-object Length -descending}
Which outputs this:
Searching D:\
Searching E:\
Searching F:\
Directory: F:\MyDirectory
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 8/13/2022 12:03 AM 0 MyFile.txt
PS C:\Windows\system32>
I would like to know how I can take the directory that is listed in the output and use it in a following command such as:
cd F:\MyDirectory
If this is possible through piping or something I would really appreciate an answer :)
Thanks for reading
I wasn't really sure what the best way to handle this would be if multiple files were found. We wouldn't be able to change directory into the parent folders while the script was running nor would we be able to do so for all of the returned files unless we opened new PowerShell windows for each. Since it appears that you will be searching for specific files which I assume will not return too many results and not knowing your ultimate goal I went with opening a new file explorer window for each file with the file being highlighted/selected.
$excludeDrives = ('C')
Get-PSDrive -PSProvider FileSystem | Where-Object { $_.Name -notin $excludeDrives } |
ForEach-Object {
Write-Host -f Green 'Searching ' $_.Root
Get-ChildItem -Path $_.Root -Recurse -Include *MyFile.txt -ErrorAction SilentlyContinue |
ForEach-Object {
# This line will open a file explorer window with the file highlighted
explorer.exe /select, $_
# This line will send the file object out through the pipeline
$_
} | Sort-Object Length -Descending
}
To answer your question about how to access the file's directory in the next command, you can use Foreach-Object and $_.Directory:
Get-ChildItem -Path $_.Root -Recurse -Include *MyFile.txt -ErrorAction SilentlyContinue |
Sort-Object Length -Descending |
ForEach-Object {
# Using the pipeline we can pass object along and access them
# using a special automatic variable called $_
# a property exists on FileInfo objects called Directory
'The directory is ' + $_.Directory
}
UPDATE
Hopefully this will answer the question in your comment
$ExclDrives = ('C')
Get-PSDrive -PSProvider FileSystem |
Where-Object { $_.Name -in $ExclDrives } |
ForEach-Object {
Write-Host -f Green 'Searching ' $_.Root
Get-ChildItem $_.Root -Include *MyFile.txt -Recurse -ErrorAction SilentlyContinue |
ForEach-Object {
# do whatever you want with the file. Reference using $_
Write-Host "Found Filename: $($_.Name)`tDirectory: $($_.Directory)" -ForegroundColor Cyan
explorer.exe /select, $_
# output the fileinfo object, in this case
# to the next command in the pipeline which is Sort-Object
$_
} |
Sort-Object Length -Descending
}
Needed to make a script that finds a list of files from a list of multiple computers. The script below works fine for that.
Now would need to add a functionality so that script goes thru ALL drives per computer, not just C$. Problem is I don't know which computers have which drives..
The current script:
$computers = Get-Content .\computers.txt
$filenames = Get-Content .\filenamelist.txt
foreach ($computer in $computers) {
foreach ($filename in $filenames) {
Get-ChildItem -Recurse -Force \\$computer\c$ -ErrorAction SilentlyContinue |
Where-Object { ($_.PSIsContainer -eq $false) -and ( $_.Name -eq "$filename") } |
Select-Object Name, Directory |
Export-Csv .\FoundFiles.csv -nti -append
}
}
So I should somehow implement command line:
$Drives = Get-PSDrive -PSProvider 'FileSystem'
So that it gets run on each computer and the Get-ChildItem line run based on the result, for each existing drive on each remote computer.
Any ideas please?
I'm trying to remove junk registry items left from adware in several machines. The adware created registry keys in the following path:
HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\
Property: a random name that is different from machine to machine
Value: a malicious string that is the same from machine to machine
Get-ChildItem -path HKLM:\System\Controlset001\Services |
Get-ItemProperty |
Where-Object {$_.ValueName -eq "Value"} |
Remove-ItemProperty -ErrorAction SilentlyContinue
I´ve tried the method above to delete keys when I know the subkey and properties, but since the property is random I am getting no results. Thank you very much!
Here's an alternative script for get-itemproperty:
# get-itemproperty2.ps1
param([parameter(ValueFromPipeline)]$key)
process {
$key.getvaluenames() |
foreach {
$value = $_
[pscustomobject] #{
Path = $key -replace 'HKEY_CURRENT_USER',
'HKCU:' -replace 'HKEY_LOCAL_MACHINE','HKLM:'
Name = $Value
Value = $Key.GetValue($Value)
Type = $Key.GetValueKind($Value)
}
}
}
So you can do something like:
Get-ChildItem HKLM:\System\Controlset001\Services | .\Get-ItemProperty2 |
Where Value -eq MyValue | Remove-ItemProperty -Whatif
Use of Get-ItemProperty isn't required for your task (and there is no need to re-implement a variation of it, the way that the currently accepted answer does).
Instead, filter by the output from the .GetValue() method applied to each subkey of the registry path of interest, assuming a known property name (registry value name):
# Specify whatever fixed value the malware stored in the 'ImagePath'
# property of the randomly named keys.
$propValue = '...'
Get-ChildItem -LiteralPath HKLM:\System\ControlSet001\Services |
Where-Object { $_.GetValue('ImagePath') -eq $propValue } |
Remove-Item -WhatIf
Note: Common parameter -WhatIf previews the operation; remove it to perform the actual operation.
Get-ChildItem -LiteralPath HKLM:\System\ControlSet001\Services enumerates all subkeys of the specified path.
Where-Object { $_.GetValue('ImagePath') -eq $propValue } selects only those subkey(s) whose ImagePath property has the specified value.
Remove-Item then removes the selected subkey(s).
If you want to find data (a property value) among all properties (registry values) of the input keys, you can use the following approach:
$propValue = '...'
Get-ChildItem -LiteralPath HKLM:\System\ControlSet001\Services |
Where-Object {
foreach ($name in $_.GetValueNames()) {
if ($_.GetValue($name) -eq $propValue) { return $true } }
}
}| Remove-Item -WhatIf
The purpose of this code is to get a list of all used executables from a specific folder. After a month we will delete any exe's not on this list.
I currently get the correct results using this:
while ($true) {
foreach ($process in Get-Process | where {$_.Path -imatch 'ksv'} | select -Unique) {
$dir = $process | Get-ChildItem;
New-Object -TypeName PSObject -Property #{
'Path' = $process.Path;
} | Out-String | Add-Content -LiteralPath Z:\processList.txt
}
Get-Content Z:\processList.txt | sort | Get-Unique > Z:\uniqueprocesslist.txt
}
I'm going to get rid of the while loop as this will be eventually running as a service.
The problem with this is that it creates a huge list in processlist.txt that I would like to eliminate to save space.
I tried to come up with a better solution that scans the text file to see if the path is written already before adding the new process path. I am not sure what I am doing wrong but nothing is ever written to the text file
while ($true) {
foreach ($process in Get-Process | where {$_.Path -imatch 'ksv'} | select -Unique) {
$dir = $process | Get-ChildItem;
$progPath = New-Object -TypeName PSObject -Property #{
'Path' = $process.Path
}
$file = Get-Content "Z:\processList.txt"
$containsLine = $file | %{$_ -match $progPath}
if ($containsLine -contains $false) {
Add-Content -LiteralPath Z:\processList.txt
}
}
}
If I understand your question correctly you want to build a "recently used" list of executables in a specific directory in a file, and update that (unique) list with each run of your script.
Something like this should do that:
$listfile = 'Z:\processlist.txt'
# Build a dictionary from known paths, so that we can check for already known
# paths with an index lookup instead of a linear search over an array.
$list = #{}
if (Test-Path -LiteralPath $listfile) {
Get-Content $listfile | ForEach-Object {
$list[$_] = $true
}
}
# List processes, expand their path, then check if the path contains the
# string "ksv" and isn't already known. Append the results to the list file.
Get-Process |
Select-Object -Expand Path |
Sort-Object -Unique |
Where-Object {$_ -like '*ksv*' -and -not $list.ContainsKey($_)} |
Add-Content $listfile
Hashtable lookup and wildcard match are used for performance reasons, because they're significantly faster than linear searches in arrays and regular expression matches.
while ($true) {
$file = Get-Content "Z:\processList.txt"
$KSVPaths = Get-Process |
Where-Object {$_.Path -imatch 'ksv'} |
Select-Object -ExpandProperty Path |
Select-Object -Unique
ForEach ($KSVPath in $KSVPaths) {
if ($KSVPath -notin $file) {
Add-Content -Path $file -Value $KSVPath
}
}
}
Hello Stackoverflow users,
I am a noob at powershell and this is part of my 1st script I am creating :). I am lost on how I would run a script that is dependent on a drive. I have script that runs task on the d: drive but some hosts does not have a D: drive but has an F: drive instead. What is the best way of adding this variable into the script?
Sample of the script is below
mkdir -Force -Path D:\Apps\NetprobeNT\Auto-monitor
Copy-Item D:\Apps\NetprobeNT\selfannounce.xml -Destination D:\Apps\NetprobeNT\Auto-monitor -Force
$removecomment = Get-Content D:\Apps\NetprobeNT\Auto-monitor\selfannounce.xml
$removecomment = $removecomment -replace "<!--<type>automonitor-windows</type>-->","" -replace "<!-- Autogenerated types -->","" -replace "<!--End of autogenerated types -->",""
$removecomment | ?{$_.Trim() -ne ""}
$removecomment | Out-File D:\Apps\NetprobeNT\Auto-monitor\selfannounce.xml -Encoding default
[xml]$selfannounceXml = Get-Content -Path D:\Apps\NetprobeNT\Auto-monitor\selfannounce.xml
$newCommentstart = $selfannounceXml.CreateComment('Autogenerated types')
$startupNode = $selfannounceXml.netprobe.selfAnnounce.managedEntity.types
$startupNode.InsertAfter($newCommentstart, $startupNode.LastChild)
$selfannounceXml.Save("D:\Apps\NetprobeNT\Auto-monitor\selfannounce.xml")
#Get IIS application path
Import-Module webadministration
$a = Get-Website | Select-Object Name
$a | ForEach-Object {
$_.name = $_.name.replace(" ","")
}
#Export file as .txt
$a | Format-Table -HideTableHeaders | Out-File D:\Apps\NetprobeNT\Auto-monitor\Website.txt
$b = Get-Content -Path D:\Apps\NetprobeNT\Auto-monitor\Website.txt
$b | ForEach {$_.TrimEnd()} | ? {$_.trim() -ne '' } > D:\Apps\NetprobeNT\Auto-monitor\Website.txt
$b = Get-Content -Path D:\Apps\NetprobeNT\Auto-monitor\Website.txt
#(ForEach ($a in $b) {$a.Replace(' ', '')}) > D:\Apps\NetprobeNT\Auto-monitor\Website.txt
#Get XML and add IIS path to 'types'
#Stop-Service -DisplayName NetprobeNT_DES
[xml]$xmlSA = Get-Content D:\Apps\NetprobeNT\Auto-monitor\selfannounce.xml
$b | ForEach-Object {
$tempchild = $xmlSA.CreateElement("type")
$tempchild.set_InnerText($_)
$newType = $xmlSA.netprobe.selfAnnounce.managedEntity.types.AppendChild($tempchild)
}
#$Newcommentstart =
$xmlSA.Save("D:\Apps\NetprobeNT\Auto-monitor\selfannounce.xml")
[xml]$selfannounceXml = Get-Content -Path D:\Apps\NetprobeNT\Auto-monitor\selfannounce.xml
$newCommentstart = $selfannounceXml.CreateComment('End of Autogenerated types')
$startupNode = $selfannounceXml.netprobe.selfAnnounce.managedEntity.types
$startupNode.InsertAfter($newCommentstart, $startupNode.LastChild)
$selfannounceXml.Save("D:\Apps\NetprobeNT\Auto-monitor\selfannounce.xml")
As you can see everything is dependent on D:\Apps.... but in some cases it might be F:\Apps..... How would I put some logic in or variable to know which drive is present? thank you for any help in advance.
Update:
From some help below, I can use the following method for now
$Path = "F:\Apps\NetprobeNT\"
$PathExists = Test-Path $Path
If ($PathExists -eq $True)
{
$DeviceID = "F:"}
Else
{
$DeviceID = "D:"}
How could I do something similar to the script above that would scan all drives and test-path to determine the $DeviceID? Note - must work for PowerShell 2.0 (windows 2003 host).
Thanks Again.
Update 2 -
I think the best method is the following as it will cater for any drive but I can not get it working. I know I am making a simple mistake -
Get-WmiObject win32_logicaldisk -Filter "DriveType=3 AND DeviceID!='C:'" | Select DeviceID | Format-Table -HideTableHeaders > c:\DeviceID.txt -Force
$DeviceID = Get-Content C:\DeviceID.txt
$DeviceID | ForEach {$_.TrimEnd()} | ? {$_.trim() -ne '' } > c:\DeviceID.txt
$DeviceID = Get-Content C:\DeviceID.txt
$Path = "$_\Apps\NetprobeNT\"
$PathExists = Test-Path $Path
foreach ($DeviceID in $DeviceID)
{
If ($PathExists -eq $True)
{
$DeviceDrive = $DeviceID}
Else
{
$DeviceDrive = "C:"}
}
I think the following line is the problem
$Path = "$_\Apps\NetprobeNT\"
Any ideas on to get this working?
Thank you
You can use WMI Filter, Filter only disk volumes which are not C Drive
$DeviceID = Get-WmiObject win32_logicaldisk -Filter "DriveType=3 AND DeviceID!='C:'" |
Select DeviceID
Then change the target:
mkdir -Force -Path "$DeviceID\Apps\NetprobeNT\Auto-monitor"
*Also, it's best practice to use one variable and call it each time, more readable, and easier, like this:
$TargetPath = "$DeviceID\Apps\NetprobeNT\Auto-monitor"
mkdir -Force -Path $TargetPath
I was able to achieve this from another post I posted. The below will make the $DeviceDrive C: drive by default. It will then search all volumes for the path and if true it will assign the drive to $DeviceDrive. Note - if two drives has the same path it will assign the last drive it finds to the variable.
$DeviceDrive = "C:"
Get-WmiObject win32_logicaldisk -Filter "DriveType=3 AND DeviceID!='C:'" |
Where-Object { Test-Path "$($_.DeviceID)\Apps\NetprobeNT\" } |
Foreach-Object {
$DeviceDrive = $_.DeviceID