This is part of the code I created:
$NameBackup = "Backup"
$DestinationDrive = ""
Get-CimInstance win32_logicaldisk |
ForEach-Object{
if ($_.VolumeName -match "$NameBackup"){
$DestinationDrive = "{0}{1}" -f $_.DeviceId,'\'
}
}
Basically it checks if there is any drive with the word "Backup" in the volume label and gets the letter of that drive.
Given this, I would like to create a code to stop the script and display an error message if there is more than one -match with the $NameBackup variable.
How can I do this?
You could use -Filter in this case, then check if the .Count property of the returned query is greater than 1, and if it is, use throw to halt your script. It may be also worth adding a new condition checking if $cim is populated.
$NameBackup = "Backup"
$cim = Get-CimInstance Win32_LogicalDisk -Filter "VolumeName LIKE '%$NameBackup%'"
if($cim.Count -gt 1) {
throw "More than one Volume Detected"
}
$DestinationDrive = $cim.DeviceID + '\'
Related
1
This is part of the code I created:
$NameOrigin = "BP |"
$NameBackup = "Backup"
Get-CimInstance win32_logicaldisk |
ForEach-Object{
if ($_.VolumeName -match "^$NameOrigin(.+)" -and $_.VolumeName -notmatch "^$NameBackup(.+)" ){
$Employee = $Matches[1]
$FSRootOrigin = "{0}{1}" -f $_.DeviceId,'\'
}
}
Basically it checks if there is any drive with the word "BP |" and that does not contain the word "Backup" in the volume label and gets the letter of that drive.
Given that, if you look at the $Employee variable, the idea is that it extracts what is after "|" so I can use it in another step of the script, however for some reason it doesn't work when the drive is named using "|".
When the $NameOrigin variable was "BP -" I was able to extract the information that was after the "-".
Why doesn't it work using "|"?
How can I get around this?
I guess the answer is to escape the pipe because it's regex for "or".
$NameOrigin = "BP \|"
Occasionally I forget to log off from a server or am disconnected through an error and I don't remember the name of the server. And my domain account starts getting periodically locked out, so I have to access logs on DC to find out which server(s) keep locking my account and log off from it/them. So I wanted to write to script in powershell that would log me off from all servers in a domain (with the exception of the server where I run the script on of course) without me needing to search which to log off from. This is what I have:
$ErrorActionPreference = "Silentlycontinue"
$Servers = (Get-ADComputer -Filter *).Name
$ScriptBlock = {
$Sessions = quser | ?{$_ -match $env:USERNAME}
if (($Sessions).Count -ge 1)
{
$SessionIDs = ($Sessions -split ' +')[2]
Write-Host "Found $(($SessionIDs).Count) user login(s) on $Server."
$SessionIDs | ForEach-Object
{
Write-Host "Logging off session [$($_)]..."
logoff $_
}
}
}
foreach ($Server in $Servers)
{
if ($Server -isnot $env:COMPUTERNAME)
{
Invoke-Command -ComputerName $Server -ScriptBlock {$ScriptBlock}
}
}
But when I launch the script, nothing happens. The script doesn't return any errors but doesn't log me off from any server, nor does it write any of the messages from Write-Host cmdlet, obviously. I noticed the $SessionIDs variable definition only returns ID of the first session. Usually this shouldn't be a problem, since it's unlikely I will have more than one session on a server, but I'd like to have this insurance. Can anyone tell me what's wrong in the script?
I notice a few things...
"First, I don't think quser | Where-Object {$_ -match $env:USERNAME} will ever return anything. The output of quser will not contain the hostname."
Try this for getting logon sessions:
$Sessions = (query user /server:$Env:ComputerName) -split "\n" -replace '\s\s+', ';' |
ConvertFrom-Csv -Delimiter ';'
Next, when you reference the $Server variable on the remote machine in your script block, it is out of scope. You would need to use $Using:Server in the script block.
Lastly, the -isnot operator doesn't compare value, it compares type. So in your last foreach, the if statement evaluates to "if type string is not type string" and will not run. Try -ne or -notlike instead.
Working with objects is much easier if you can just parse the output of QUser.exe. Given your scenario, here's my take on it:
$servers = (Get-ADComputer -Filter '*').Name.Where{$_ -ne $env:COMPUTERNAME}
foreach ($server in $servers)
{
if (-not ($quser = ((QUser.exe /server:$server) -replace '\s{20,39}',',,' -replace '\s{2,}',',' 2>&1) | Where-Object -FilterScript { $_ -match $env:USERNAME })) {
Continue
}
Write-Verbose -Message "$($quser.Count) session(s) found on $server." -Verbose
($quser.Trim() | ConvertFrom-Csv -Header 'USERNAME','SESSIONNAME','ID','STATE','IDLE TIME','LOGON TIME').foreach{
Write-Verbose -Message "Logging user [$($_.UserName)] off." -Verbose
LogOff.exe $_.ID /server:$server
}
}
Filtering should always happen before hand meaning, filter out your computer name on your first call to Get-ADComputer. Since you're using QUser.exe and LogOff.exe to begin with, I'd recommend the use of it all the way through since LogOff accepts an ID value that QUser outputs.
Next, placing the call to quser inside your if statement does two things in this case.
Filters for all users matching $ENV:UserName
Returns $true if anything is found, and $false if not found.
So, switching the results using -not will turn $false into $true allowing the execution of the code block which will just continue to the next server.
This in turn doesn't bother with the rest of the code and continues onto the next computer if no matching names were found.
The use of $quser inside the if statement is so you can save the results to it if more than one name is found; (..) allows this as it turns the variable assignment into an expression having the output pass through onto the pipeline where it is either empty, or not.
Finally, referencing the $quser variable we can convert the strings into objects piping to ConvertFrom-Csv. Only step left to do is iterate through each row and passing it over to LogOff to perform the actual logoff.
If you've noticed, the headers are manually-specified because it is filtered out by the Where-Object cmdlet. This is a better approach seeing as there could be "more than one" RDP Session, now you're just left with those sessions matching the name which can be saved to $quser, so no extra filtering is needed down the line.
So I modified the script this way and it works, sort of. It logs off account from servers, which is the main goal. There are still some glitches, like the message it sends from the first Write-Host doesn't give server's name, the message from second one gives a different value than it should (it gives [1] value after -split instead of [2] for some reason; but those are not really that important things, even though I will try to make at least the first message right) and $SessionIDs still gives only the first value, but usually you shouldn't have more than one RDP session per server. I've seen more sessions of one user, but that is very rare. But I'd also like to fix this if possible. Nevertheless, the script basically does the most important thing. But if someone has a suggestion how to fix the glitches I mentioned I would be grateful.
$ErrorActionPreference = "Silentlycontinue"
$Servers = (Get-ADComputer -Filter *).Name
$ScriptBlock = {
$Sessions = quser | ?{$_ -match $env:USERNAME}
if (($Sessions).Count -ge 1)
{
$SessionIDs = , ($Sessions -split ' +')[2]
Write-Host "Found $(($SessionIDs).Count) user login(s) on $Server."
Foreach ($SessionID in $SessionIDs)
{
Write-Host "Logging off session $SessionID..."
logoff $SessionID
}
}
}
foreach ($Server in $Servers)
{
if ($Server -ne $env:COMPUTERNAME)
{
Invoke-Command -ComputerName $Server -ScriptBlock $ScriptBlock
}
}
I am blocked with my code
I will try to explain what I would like to do with it,
my code is to scan the different windows services, to keep in memory only what uses an .exe and then to search among them the ones that the users have full control of.
I would like it to display the service and its rights at the end.
$services = Get-WmiObject win32_service | ?{$_.PathName -like '*.exe*'} | select Name, State, Pathname, StartName | Out-Null
foreach ($service in $services) {
$var = "{0}.exe" -f ($Service.PathName -Split ".exe")[0]
foreach ($right in $var ){
if ( (Get-Acl $var).Access -ccontains "BUILTIN\Utilisateurs FullControl "{
Write-Warning " Exploit detected "
}
}
}
thank you in advance for your feedback
Get-CimInstance Win32_Service |
Where-Object { $_.PathName -like '*.exe*'} |
Select-Object Name, State, Pathname, StartName |
ForEach-Object {
$_.PathName = ($_.PathName -split '(?<=\.exe\b)')[0].Trim('"')
Add-Member -PassThru -InputObject $_ Acl (Get-Acl -LiteralPath $_.PathName)
} |
Where-Object {
$_.Acl.Access.Where({
$_.IdentityReference -ceq 'BUILTIN\Utilisateurs' -and
$_.FileSystemRights -eq 'FullControl'
}, 'First').Count -gt 0
}
Note that I've replaced Get-WmiObject with Get-CimInstance, because the CIM cmdlets superseded the WMI cmdlets in PowerShell v3 (released in September 2012). Therefore, the WMI cmdlets should be avoided, not least because PowerShell (Core) (v6+), where all future effort will go, doesn't even have them anymore. Note that WMI still underlies the CIM cmdlets, however. For more information, see this answer.
The above uses the ForEach-Object call to:
update the .PathName property of each object to contain only the - unquoted - path of the executable with each service.
add an .Acl property to each object via Add-Member, containing the service executable's ACL, obtained via Get-Acl.
The resulting list of objects is then filtered by those whose service-executable ACL contains an entry for identity BUILTIN\Utilisateurs[1] with full control over the executable.
That is, the resulting objects are effectively those for which you meant to issue Write-Warning " Exploit detected "
As for what you tried:
$services = ... | Out-Null by definition captures nothing[2] in variable $services, given that Out-Null's purpose is to suppress output.
While $var = "{0}.exe" -f ($Service.PathName -Split ".exe")[0] does extract the executable path (although .exe should be \.exe\b, for robustness), it may include enclosing double quotes, which should be stripped.
It's unclear where $rights comes from.
You cannot use -ccontains to match across multiple properties of an object, and note that the purpose of the -contains operator and its variants is to test presence of a value in full, in a collection, not to look for a substring in a single string.
[1] It's interesting to see that these identity references are localized; the equivalent on a US-English system would be BUILTIN\Users. Generally, it would be better to obtain a culture-independent representation of this identity, namely its SID, and use that for comparison: $_.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]).Value -eq 'S-1-5-32-545'
[2] Loosely speaking, $null; technically, it is the singleton value that PowerShell uses to signal "no output received", [System.Management.Automation.Internal.AutomationNull]::Value.
If I begin by the begining you terminate your first line by | out-null so $services contains nothing.
Then you forgot a ")" in your if.
You should present your code with indentations.
Be carefull :
"{0}.exe" -f ("C:\WINDOWS\system32\msiexec.exe /V"-Split ".exe")[0]
Gives :
C:\WINDOWS\system32\ms.exe
So use a '\' before the '.' :
"{0}.exe" -f ("C:\WINDOWS\system32\msiexec.exe /V"-Split "\.exe")[0]
which gives (regular expression story ?):
C:\WINDOWS\system32\msiexec.exe
So it will give something like that.
$services = Get-WmiObject win32_service | ?{$_.PathName -like '*.exe*'} | select Name, State, Pathname, StartName
foreach ($service in $services) {
$var = "{0}.exe" -f ($Service.PathName -Split "\.exe")[0]
if ((Get-Acl $var.Trim('"') -ErrorAction Stop) -ccontains "BUILTIN\Utilisateurs FullControl "){
Write-Warning " Exploit detected "
}
}
I am trying to create a GUI for my script that will go and delete certain files that match a certain name that is older than a set time period in a directory and show a what if before deleting. Everything was going well until I tried to put the output into a Textbox. The output displays in the console fine but won't display in the textbox. I have narrowed it down to the command I am running, as if I simply remove it and run 'ping google.com' it outputs fine. Please find my code below:
$scanbutton.Location = '380,84'
$scanbutton.text = 'Scan Directory'
$scanbutton.height = 25
$scanbutton.Width = 100
$scanbutton.Add_Click({
$result.Text = get-childitem $folderBrowser.SelectedPath -include "cat*.png" -force -recurse | where-object { (-not $_.PSIsContainer) -and ($_.LastWriteTime -lt (get-date).AddDays(-0)) } | remove-item -whatif
#$result.Text = ping google.com
$Form.Controls.Add($result)
})
Anyone have any ideas why this is? I am still very new to all this so please be nice. Also how can I get the output to follow new lines like in the console? At the moment it just has it as one long string (when I do ping). Please let me know if you need anything else from me.
Thank you in advance.
IC
As commented, the Remove-Item cmdlet does not return anything you can capture as text in a textbox. Neither does the -WhatIf switch, which like Write-Host is designed to not return anything, but write directly onto the console.
In your case, you can create and write your own info in the TextBox. Something like:
$resultBox = [System.Windows.Forms.TextBox]::new()
# do the Location, Size and whatever it takes here
# make the textbox accept multiple lines of text
$resultBox.Multiline = $true
# add the control to the form after you have created it
# NOT inside the $scanbutton.Add_Click() event handler
$Form.Controls.Add($resultBox)
$scanbutton.Add_Click({
# get a list of files to remove (just the FullNames)
$filesToRemove = Get-ChildItem $folderBrowser.SelectedPath -Filter "cat*.png" -File -Force -Recurse |
Where-Object { ($_.LastWriteTime -lt (Get-Date).AddDays(-60).Date) } |
Select-Object -ExpandProperty FullName
# write the file FullNames in the textbox
$resultBox.Text = "Removing files:`r`n{0}" -f ($filesToRemove -join [environment]::NewLine)
$filesToRemove | Remove-Item -WhatIf
})
I'm having trouble terminating a foreach-object loop in PowerShell v2. For a rough idea of the task I'm trying to accomplish, here's the pseudo-code:
Read lists of host machines from a text file
For each host in the text file get Win32_Product (filtered against an exclusion list),
convert output to html and save.
The reason for the script is that I've amassed a text file listing all applications included on standard client images, and would like to periodically scan hosts from another text file to see if there are any unauthorized, sketchy or otherwise unnecessary applications on the host machines.
The code does work in a rough sense, but the main issue I'm having is that the script will not terminate without manual intervention. I guess the component I'm missing here is to run the loop until some condition exists (ie. first line in the host file is encountered for the second time), then terminates the script. Although this is the method I've envisioned, I am always open to other logic, especially if its more efficient.
Here's the actual code:
Get-Content c:\path\to\testhostlist.txt | Foreach-Object {
Get-WmiObject Win32_Product |
Where-Object { $_.Name -f "'C:\path\to\testauthapplist.txt'" |
ConvertTo-Html name,vendor,version -title $name -body "<H2>Unauthorized Applications.</H2>"}} |
Set-Content c:\path\to\unauthapplisttest.html
I don't see how the first line of the host file (I infer you mean testhostlist.tx) would ever be encountered a second time, since you're only listing it once. This doesn't even seem to be an infinite loop that would need an exit condition. Foreach-Object doesn't repeat indefinitely.
It seems to me that the problem is not that the loop doesn't exit without a condition, it's that the syntax is invalid.
Where-Object filters the pipeline by passing only objects that meet a certain condition, but the scriptblock that follows doesn't perform a boolean test.
In fact, the content of the scriptblock doesn't appear valid in and of itself. -f is the format operator, and takes a format string as the left operand, but $_.Name is not a format string.
I'm going to take a guess here, based on your description, that the idea is to filter the results of Get-WmiObject Win32_Product for objects whose Name property isn't listed in testauthapplist.txt (I take it that's the "exclusion list" you're referring to). If so, this is the correct syntax:
Get-Content c:\path\to\testhostlist.txt | %{
Get-WmiObject Win32_Product | ?{
(Get-Content 'C:\path\to\testauthapplist.txt') -notcontains $_.Name
} | ConvertTo-Html name,vendor,version -title $name -body "<H2>Unauthorized Applications.</H2>"
} | Set-Content c:\path\to\unauthapplisttest.html
(Note that %{} and ?{} are just abbreviations for Foreach-Object and Where-Object, respectively.)
If i understood you correctly you are trying to stop your Script completely? If so did you try Break?
If you only want to skip a loop use continue
$hostlist = Get-Content c:\path\to\testhostlist.txt
$a = #()
Foreach($item in $hostlist)
{
$a += "<style>"
$a += "BODY{background-color:gray;}"
$a += "TABLE{margin: auto;border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse;}"
$a += "TH{border-width: 1px;padding: 4px;border-style: solid;border-color: black;background-color:yellow}"
$a += "TD{border-width: 1px;padding: 4px;border-style: solid;border-color: black;background-color:white}"
$a += "h2{color:#fff;}"
$a += "</style>"
Get-WmiObject Win32_Product | select name,vendor,version | sort name | ConvertTo-Html -head $a -body "<Center><H2>Unauthorized Applications.</H2></Center>" | Out-File c:\path\to\$item"-applist.html"
}