This question already has an answer here:
PowerShell output is crossing between functions
(1 answer)
Closed 2 years ago.
I created a script to read a list of names and translate them to IP by using "Resolve-DnsName".
I want users to just double click the script and read the output.
cls
Write-Host -ForegroundColor Green -BackgroundColor DarkRed "
______________________________
|X | Y | Z |
|-------+ ------+--------------|
|F2M1 | 49621 |esbsa9908ee43 |
|F2M2 | 47546 |esbsa009908jc1|
|F2M3 |xxxxxxx|7417191543366 |
|F2M4 | 47516 |esbsa9908ee18 |
|F2M5 | 47385 |7417191543116 |
|Capital| 86658 |7417191543242 |
|______________________________|"
Write-Host "`n`n"
$estacoes = #(
'esbsa9908ee43',
'esbsa009908jc1',
'7417191543366',
'esbsa9908ee18',
'7417191543116',
'7417191543242'
)
ForEach ($estacao in $estacoes){
Resolve-DnsName -ErrorAction Continue -Type A -QuickTimeout -Name $estacao | Select-Object Name,IpAddress
}
Pause
It happens that the "pause" command at the end is being executed before "foreach".
______________________________
|X | Y | Z |
|-------+ ------+--------------|
|F2M1 | 49621 |esbsa9908ee43 |
|F2M2 | 47546 |esbsa009908jc1|
|F2M3 |xxxxxxx|7417191543366 |
|F2M4 | 47516 |esbsa9908ee18 |
|F2M5 | 47385 |7417191543116 |
|Capital| 86658 |7417191543242 |
|______________________________|
Press Enter to continue...:
Name IPAddress
---- ---------
esbsa9908ee43 172.18.18.215
esbsa009908jc1 172.18.18.44
7417191543366 172.18.18.18
esbsa9908ee18 172.18.18.21
7417191543116 172.18.18.126
7417191543242 172.30.165.50
I can't seem to find a reason for that.
Can you guys help out?
It looks similar to the funny things that happen with pipe output and write-host output, where the write-host output will come out first even though it's run after the pipeline. Here's one way to change the order of the output:
$estacoes = echo microsoft.com yahoo.com
$result = ForEach ($estacao in $estacoes){
Resolve-DnsName -ErrorAction Continue -Type A -QuickTimeout -Name $estacao |
Select-Object Name,IpAddress
}
$result | format-table
pause
# didn't work
# 'Press Enter to continue...:'
# [void][console]::readkey()
Related
I'm trying to use PowerShell to tell me when my computer is on battery or AC Power.
I want my script to send me a windows notification when my laptop's charger unplugs.
For the moment, I try to use a recursive fonction to test my battery status every 5 seconds but it doesn't work...
Please, be indulgent about my level, I didn't know anything about PowerShell 3 hours ago... And the last time I coded something was a long time ago !
Function Test-IsOnBattery
{
$battery = Get-WmiObject Win32_Battery
If ($battery.BatteryStatus -eq 2) {
Write-Host "PC sur secteur."
Start-Sleep -Seconds 5
return Test-IsOnBattery
}
Else {
Write-Host "PC sur batterie."
New-BurntToastNotification -Text "Battery Notification" , "Batterie plus sur secteur !"
}
}
Nathan,
Here's a script you can use that you can run from a Scheduled Task, rather than a loop, and have it start on boot up and repeat every so many minutes.
<#+---------------------------------------------------------------------------+
| PowerShell Pgm: BatteryStatus.ps1 |
| Programmed By : The Computer Mentor |
| aka : RetiredGeek # askWoody.com & StackOverFlow.com |
| Created : 06 Mar 2013 |
| Last Updated : 23 Jan 2023 | |
| Current Ver. : 6.0 |
+---------------------------------------------------------------------------+
#>
Clear-Host
Add-Type -AssemblyName "System.Windows.Forms"
$StatusMsg = {
[Windows.Forms.MessageBox]::Show($Message, $Title,
[Windows.Forms.MessageBoxButtons]::OK ,
[Windows.Forms.MessageBoxIcon]::Information)}
$Message = ""
$Title = "Battery Status:"
<#+-----------------------------------------------------+
| BatterStatus Values |
|Other (1) The battery is discharging. |
|Unknown (2) The system has access to AC so no |
| battery is being discharged. However, |
| the battery is not necessarily charging.|
|Fully Charged (3) |
|Low (4) |
|Critical (5) |
|Charging (6) |
|Charging and High (7) |
|Charging and Low (8) |
|Charging and Critical (9) |
|Undefined (10) |
|Partially Charged (11) |
+-----------------------------------------------------|#>
$GWArgs = #{ Class = "Win32_Battery"
ComputerName = "LocalHost"
}
$MyBattery = Get-CIMInstance #GWArgs
If ($Null -eq $MyBattery) {
$Message = "No Battery Present"
}
Else {
$BatteryRemaining = [Int]$MyBattery.EstimatedChargeRemaining
if(($BatteryRemaining -lt 30) -and
$($MyBattery.BatteryStatus) -eq 1) {
$Message = "Battery Low...Please Charge Me!"
}
Elseif(($BatteryRemaining -gt 90) -and
$($MyBattery.BatteryStatus) -ne 1) {
$Message =
"Battery CHARGED above 90%...Please Unplug Me!"
}
} #End Else
if($Message -ne "") {
$Null = & $StatusMsg
}
<#
+----------------------------------------------------------+
| Notes: |
| 1. To call as a scheduled task do the following in the |
| Action Pane |
| A. Action: Start a Program |
| B. Program/script: powershell.exe |
| D. Set the Trigger run At Logon and then repeat |
| every few minutes. |
+----------------------------------------------------------+
#>
If you want to use Toast msgs. just replace that logic where I have my $StatusMgs lines.
I am working with SCOM (System Center Operations Manager).
There is the posibility to query SCOM with powershell.
I tried it and its working good, but I need your help.
I use the following command:
Get-SCOMClass -name Microsoft.Windows.Client.Win10.LogicalDisk |
Get-SCOMClassInstance |
Select-Object *
Here is the output of this:
If I want only the object HealthState thats no problem. Just use this command:
Get-SCOMClass -name Microsoft.Windows.Client.Win10.LogicalDisk |
Get-SCOMClassInstance |
Select-Object HealthState
But how can I get the output of the first entry (FreeSpace)?
[Microsoft.Windows.Client.Win10.Aggregate.LogicalDisk].FreeSpace
I tried several things as using the SCOMClass "Microsoft.Windows.Client.Win10.Aggregate.LogicalDisk" directly, but its the same output as for "Microsoft.Windows.Client.Win10.LogicalDisk".
This should work:
Get-SCOMClass -name Microsoft.Windows.Client.Win10.LogicalDisk | Get-SCOMClassInstance | Select-Object #{Name="Computer";Expression={$_.'[Microsoft.Windows.Computer].PrincipalName'.value}}, #{Name="Logical Disk";Expression={$_.DisplayName}}, #{Name="Free Size in GB";Expression={[math]::round($_.'[Microsoft.Windows.Client.Win10.Aggregate.LogicalDisk].FreeSpace'.value/1GB, 2)}}
Try this:
Get-SCOMClass -name Microsoft.Windows.Client.Win10.LogicalDisk | Get-SCOMClassInstance | Where-Object {$_.HealthState -like "*FreeSpace"}
I am using PowerShell to collect lists of names from multiple text files. May of the names in these files are similar / repeating. I am trying to ensure that PowerShell returns a single text file with all of the unique items. In looking at the data it looks like the script is gathering 271/296 of the unique items. I'm guessing that some of the data is being flagged as duplicates when it shouldn't, any suggestions?
#Take content of each file (all names) and add unique values to text file
#for each unique value, create a row & check to see which txt files contain
function List {
$nofiles = Read-Host "How many files are we pulling from?"
$data = #()
for ($i = 0;$i -lt $nofiles; $i++)
{
$data += Read-Host "Give me the file name for file # $($i+1)"
}
return $data
}
function Aggregate ($array) {
Get-Content $array | Sort-Object -unique | Out-File newaggregate.txt
}
#SCRIPT BODY
$data = List
aggregate ($data)
I was expecting this code to catch everything, but it's missing some items that look very similar. List of missing names and their similar match:
CORPINZUTL16 MISSING FROM OUTFILE
CORPINZTRACE MISSING FROM OUTFILE
CORPINZADMIN Found In File
I have about 20 examples like this one. Apparently the Get-Content -Unique is not checking every character in a line. Can anyone recommend a better way of checking each line or possibly forcing the get-character to check full names?
Just for demonstration this line creates 3 txt files with numbers
for($i=1;$i -lt 4;$i++){set-content -path "$i.txt" -value ($i..$($i+7))}
1.txt | 2.txt | 3.txt | newaggregate.txt
1 | | | 1
2 | 2 | | 2
3 | 3 | 3 | 3
4 | 4 | 4 | 4
5 | 5 | 5 | 5
6 | 6 | 6 | 6
7 | 7 | 7 | 7
8 | 8 | 8 | 8
| 9 | 9 | 9
| | 10 | 10
Here using Get-Content with a range [1-3] of files
Get-Content [1-3].txt | Sort-Object {[int]$_} -Unique | Out-File newaggregate.txt
$All = Get-Content .\newaggregate.txt
foreach ($file in (Get-ChildItem [1-3].txt)){
Compare-Object $All (Get-Content $file.FullName) |
Select-Object #{n='File';e={$File}},
#{n="Missing";e={$_.InputObject}} -ExcludeProperty SideIndicator
}
File Missing
---- -------
Q:\Test\2019\05\07\1.txt 9
Q:\Test\2019\05\07\1.txt 10
Q:\Test\2019\05\07\2.txt 1
Q:\Test\2019\05\07\2.txt 10
Q:\Test\2019\05\07\3.txt 1
Q:\Test\2019\05\07\3.txt 2
there are two ways to achieve this one is using select-object -Unique which works when data is not sorted and can be used for small data or lists.
When dealing with large files we can use get-Unique command which works with sorted input, if input data is not sorted then it will give wrong results.
Get-ChildItem *.txt | Get-Content | measure -Line #225949
Get-ChildItem *.txt | Get-Content | sort | Get-Unique | measure -Line #119650
Here is my command for multiple files :
Get-ChildItem *.txt | Get-Content | sort | Get-Unique >> Unique.txt
Our users sometimes gives us misspelled names/usernames and I would like to be able to search active directory for a near match, sorting by closest (any algorithm would be fine).
For example, if I try
Get-Aduser -Filter {GivenName -like "Jack"}
I can find the user Jack, but not if I use "Jacck" or "ack"
Is there a simple way to do this?
You can calculate the Levenshtein distance between the two strings and make sure it's under a certain threshold (probably 1 or 2). There is a powershell example here:
Levenshtein distance in powershell
Examples:
Jack and Jacck have an LD of 1.
Jack and ack have an LD of 1.
Palle and Havnefoged have an LD of 8.
Interesting question and answers. But a possible simpler solution is to search by more than one attribute as I would hope most people would spell one of their names properly :)
Get-ADUser -Filter {GivenName -like "FirstName" -or SurName -Like "SecondName"}
The Soundex algorithm is designed for just this situation. Here is some PowerShell code that might help:
Get-Soundex.ps1
OK, based on the great answers that I got (thanks #boxdog and #Palle Due) I am posting a more complete one.
Major source: https://github.com/gravejester/Communary.PASM - PowerShell Approximate String Matching. Great Module for this topic.
1) FuzzyMatchScore function
source: https://github.com/gravejester/Communary.PASM/tree/master/Functions
# download functions to the temp folder
$urls =
"https://raw.githubusercontent.com/gravejester/Communary.PASM/master/Functions/Get-CommonPrefix.ps1" ,
"https://raw.githubusercontent.com/gravejester/Communary.PASM/master/Functions/Get-LevenshteinDistance.ps1" ,
"https://raw.githubusercontent.com/gravejester/Communary.PASM/master/Functions/Get-LongestCommonSubstring.ps1" ,
"https://raw.githubusercontent.com/gravejester/Communary.PASM/master/Functions/Get-FuzzyMatchScore.ps1"
$paths = $urls | %{$_.split("\/")|select -last 1| %{"$env:TEMP\$_"}}
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
for($i=0;$i -lt $urls.count;$i++){
Invoke-WebRequest -Uri $urls[$i] -OutFile $paths[$i]
}
# concatenating the functions so we don't have to deal with source permissions
foreach($path in $paths){
cat $path | Add-Content "$env:TEMP\Fuzzy_score_functions.ps1"
}
# to save for later, open the temp folder with: Invoke-Item $env:TEMP
# then copy "Fuzzy_score_functions.ps1" somewhere else
# source Fuzzy_score_functions.ps1
. "$env:TEMP\Fuzzy_score_functions.ps1"
Simple test:
Get-FuzzyMatchScore "a" "abc" # 98
Create a score function:
## start function
function get_score{
param($searchQuery,$searchData,$nlist,[switch]$levd)
if($nlist -eq $null){$nlist = 10}
$scores = foreach($string in $searchData){
Try{
if($levd){
$score = Get-LevenshteinDistance $searchQuery $string }
else{
$score = Get-FuzzyMatchScore -Search $searchQuery -String $string }
Write-Output (,([PSCustomObject][Ordered] #{
Score = $score
Result = $string
}))
$I = $searchData.indexof($string)/$searchData.count*100
$I = [math]::Round($I)
Write-Progress -Activity "Search in Progress" -Status "$I% Complete:" -PercentComplete $I
}Catch{Continue}
}
if($levd) { $scores | Sort-Object Score,Result |select -First $nlist }
else {$scores | Sort-Object Score,Result -Descending |select -First $nlist }
} ## end function
Examples
get_score "Karolin" #("Kathrin","Jane","John","Cameron")
# check the difference between Fuzzy and LevenshteinDistance mode
$names = "Ferris","Cameron","Sloane","Jeanie","Edward","Tom","Katie","Grace"
"Fuzzy"; get_score "Cam" $names
"Levenshtein"; get_score "Cam" $names -levd
Test the performance on a big dataset
## donload baby-names
$url = "https://github.com/hadley/data-baby-names/raw/master/baby-names.csv"
$output = "$env:TEMP\baby-names.csv"
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Invoke-WebRequest -Uri $url -OutFile $output
$babynames = import-csv "$env:TEMP\baby-names.csv"
$babynames.count # 258000 lines
$babynames[0..3] # year, name, percent, sex
$searchdata = $babynames.name[0..499]
$query = "Waren" # missing letter
"Fuzzy"; get_score $query $searchdata
"Levenshtein"; get_score $query $searchdata -levd
$query = "Jon" # missing letter
"Fuzzy"; get_score $query $searchdata
"Levenshtein"; get_score $query $searchdata -levd
$query = "Howie" # lookalike
"Fuzzy"; get_score $query $searchdata;
"Levenshtein"; get_score $query $searchdata -levd
Test
$query = "John"
$res = for($i=1;$i -le 10;$i++){
$searchdata = $babynames.name[0..($i*100-1)]
$meas = measure-command{$res = get_score $query $searchdata}
write-host $i
Write-Output (,([PSCustomObject][Ordered] #{
N = $i*100
MS = $meas.Milliseconds
MS_per_line = [math]::Round($meas.Milliseconds/$searchdata.Count,2)
}))
}
$res
+------+-----+-------------+
| N | MS | MS_per_line |
| - | -- | ----------- |
| 100 | 696 | 6.96 |
| 200 | 544 | 2.72 |
| 300 | 336 | 1.12 |
| 400 | 6 | 0.02 |
| 500 | 718 | 1.44 |
| 600 | 452 | 0.75 |
| 700 | 224 | 0.32 |
| 800 | 912 | 1.14 |
| 900 | 718 | 0.8 |
| 1000 | 417 | 0.42 |
+------+-----+-------------+
These times are quite crazy, if anyone understand why please comment on it.
2) Generate a table of Names from Active Directory
The best way to do this depends on the organization of the AD. Here we have many OUs, but common users will be in Users and DisabledUsers. Also Domain and DC will be different (I'm changing ours here to <domain> and <DC>).
# One way to get a List of OUs
Get-ADOrganizationalUnit -Filter * -Properties CanonicalName |
Select-Object -Property CanonicalName
then you can use Where-Object -FilterScript {} to filter per OU
# example, saving on the temp folder
Get-ADUser -f * |
Where-Object -FilterScript {
($_.DistinguishedName -match "CN=\w*,OU=DisabledUsers,DC=<domain>,DC=<DC>" -or
$_.DistinguishedName -match "CN=\w*,OU=Users,DC=<domain>,DC=<DC>") -and
$_.GivenName -ne $null #remove users without givenname, like test users
} |
select #{n="Fullname";e={$_.GivenName+" "+$_.Surname}},
GivenName,Surname,SamAccountName |
Export-CSV -Path "$env:TEMP\all_Users.csv" -NoTypeInformation
# you can open the file to inspect
Invoke-Item "$env:TEMP\all_Users.csv"
# import
$allusers = Import-Csv "$env:TEMP\all_Users.csv"
$allusers.Count # number of lines
Usage:
get_score "Jane Done" $allusers.fullname 15 # return the 15 first
get_score "jdoe" $allusers.samaccountname 15
I have the following powershell script to count lines per file in a given directory:
dir -Include *.csv -Recurse | foreach{get-content $_ | measure-object -line}
This is giving me the following output:
Lines Words Characters Property
----- ----- ---------- --------
27
90
11
95
449
...
The counts-per-file is fine (I don't require words, characters, or property), but I don't know what filename the count is for.
The ideal output would be something like:
Filename Lines
-------- -----
Filename1.txt 27
Filename1.txt 90
Filename1.txt 11
Filename1.txt 95
Filename1.txt 449
...
How do I add the filename to the output?
try this:
dir -Include *.csv -Recurse |
% { $_ | select name, #{n="lines";e={
get-content $_ |
measure-object -line |
select -expa lines }
}
} | ft -AutoSize
I can offer another solution :
Get-ChildItem $testPath | % {
$_ | Select-Object -Property 'Name', #{
label = 'Lines'; expression = {
($_ | Get-Content).Length
}
}
}
I operate on the. TXT file, the return value is like this ↓
Name Lines
---- ----
1.txt 1
2.txt 2
3.txt 3
4.txt 4
5.txt 5
6.txt 6
7.txt 7
8.txt 8
9.txt 9
The reason why I want to sort like this is that I am rewriting a UNIX shell command (from The Pragmatic Programmer: Your Journey to Mastery on page 145).
The purpose of this command is to find out the five files with the largest number of lines.
At present, my progress is the above content,i'm close to success.
However, this command is far more complicated than the UNIX shell command!
I believe there should be a simpler way, I'm trying to find it.
find . -type f | xargs wc -l | sort -n | tail -5
I have used the following script that gives me lines in files of all sub directories in folder c:\temp\A. The output is in lines1.txt file. I have applied a filer to choose only file types of ".TXT".
Get-ChildItem c:\temp\A -recurse | where {$_.extension -eq ".txt"} | % {
$_ | Select-Object -Property 'Name', #{
label = 'Lines'; expression = {
($_ | Get-Content).Length
}
}
} | out-file C:\temp\lines1.txt