Powershell loop exits early - windows

This is extremely stupid, but my loop is exiting before it gets to the second loop apparently.
What the script does is counts the hardware bus and function number from ethernet cards on a server (usually contain at least 2 ports or more). The bus usually corresponds to a chip/card in the server, and the function number usually the specific port.
Example scenario - a server with 2 dual-port ethernet cards might look like this when you run get-netadapterhardwareinfo:
name Bus Function
---- --- --------
PORT4 2 1
PORT3 2 0
B1 Port2 1 1
B1 Port1 1 0
You can see the script runs on "bus 1" but then the loop exits before "bus 2" can be processed.
This line is supposed to handle it
for($i = 1; $i -le $buscount.Maximum; $i++)
and the "less than or equal" should evaluate as true for the second loop. I put in some write-hosts at the end to debug the loop, it seems to exit.
FULL SCRIPT:
#BEGIN
#collect hardware information, determine ports.
$allnicinfo = Get-NetAdapterHardwareInfo | Select Name, InstanceID, InterfaceDescription, BusNumber, FunctionNumber
#determine how many adapters exist
$buscount = Get-NetAdapterHardwareInfo | select -ExpandProperty busnumber | measure-object -maximum | select Maximum
#improve the bus count method
for($i = 1; $i -le $buscount.Maximum; $i++)
{
$bus = $i
$funccount= #()
$funccount = $allnicinfo | where-object {$_.busnumber -eq $i} | select -expandproperty functionnumber | measure-object -Maximum | select Maximum
for($i = 0; $i -le $funccount.Maximum; $i++)
{
$nic = Get-NetAdapterHardwareInfo | where {($_.busnumber -eq $bus) -and ($_.functionnumber -eq $i)}
$port = $i + 1
Rename-NetAdapter $nic.Name "B$bus Port$port"
}
write-host $bus
write-host $i
}

Following your logic, here's a cleaned up version of your script that should work better. First we create an array of $Nics to perform calculations against so you don't need to constantly poll your hardware (slow). It's also a bad practice to mask variable names (using $i in both of your loops).
## Collect hardware information, determine ports
$Nics = #(
Get-NetAdapterHardwareInfo |
Select-Object -Property #('Name', 'InstanceID', 'InterfaceDescription', 'BusNumber', 'FunctionNumber')
)
## Rename logic
For ($i = 0; $i -le $Nics.Count; $i++)
{
$FunctionCount = (($Nics | Where-Object { $_.BusNumber -eq $i }).FunctionNumber | Measure-Object -Maximum).Maximum
For ($j = 0; $j -le $FunctionCount; $j++)
{
$Nic = $Nics | Where-Object { $_.BusNumber -eq $i -and $_.FunctionNumber -eq $j }
Rename-NetAdapter -Name $Nic.Name -NewName "B$i Port$($j + 1)" -PassThru -WhatIf
}
}
This is safe to run, do note -WhatIf

I was a little confused by your code and think you could do it a little simpler. See below
$NetAdapterHardwareInfo = Get-NetAdapterHardwareInfo
foreach ( $NetAdapter in $NetAdapterHardwareInfo )
{
$NewName = 'B{0} Port{1}' -f $NetAdapter.Bus,($NetAdapter.Function + 1)
Rename-NetAdapter $NetAdapter.Name -NewName $NewName -WhatIf
}
If you leave the -WhatIf you would get output like this:
What if: Rename-NetAdapter -Name 'Wi-Fi' -NewName 'B58 Port1'
What if: Rename-NetAdapter -Name 'Ethernet' -NewName 'B0 Port7'
You can just remove the -WhatIf when you want to put it in production.

Related

Powershell file updating with future and past time

I am having a text file that has content in this manner.
One;Thomas;Newyork;2020-12-31 14:00:00;0
Two;David;London;2021-01-31 12:00:00;0
Three;James;Chicago;2021-01-20 15:00:00;0
Four;Edward;India;2020-12-25 15:00:00;0
In these entries according to date time, two are past entries and two are future entries. The last 0 in the string indicates the Flag. With the past entries that flag needs to be changed to 1.
Consider all the entries are separated with the array. I tried this block of code but its not working to solve the problem here.
for ($item=0 ; $item -lt $entries.count ; $item++)
{
if ($entries.DateTime[$item] -lt (Get-Date -Format "yyyy-MM-dd HH:mm:ss"))
{
$cont = Get-Content $entries -ErrorAction Stop
$string = $entries.number[$item] + ";" + $entries.name[$item] + ";" +
$entries.city[$item]+ ";" + $entries.DateTime[$item]
$lineNum = $cont | Select-String $string
$line = $lineNum.LineNumber + 1
$cont[$line] = $string + ";1"
Set-Content -path $entries
}
}
I am getting errors with this concept.
Output should come as:-
One;Thomas;Newyork;2020-12-31 14:00:00;1 ((Past Deployment with respect to current date)
Two;David;London;2021-01-31 12:00:00;0
Three;James;Chicago;2021-01-20 15:00:00;0
Four;Edward;India;2020-12-25 15:00:00;1 (Past Deployment with respect to current date)
This output needs to be overwritten on the file from where the content is extracted ie Entries.txt
param(
$exampleFileName = "d:\tmp\file.txt"
)
#"
One;Thomas;Newyork;2020-12-31 14:00:00;0
Two;David;London;2021-01-31 12:00:00;0
Three;James;Chicago;2021-01-20 15:00:00;0
Four;Edward;India;2020-12-25 15:00:00;0
"# | Out-File $exampleFileName
Remove-Variable out -ErrorAction SilentlyContinue
Get-Content $exampleFileName | ForEach-Object {
$out += ($_ -and [datetime]::Parse(($_ -split ";")[3]) -gt [datetime]::Now) ? $_.SubString(0,$_.Length-1) + "1`r`n" : $_ + "`r`n"
}
Out-File -InputObject $out -FilePath $exampleFileName

PowerShell - Refering to variable outside for loop

I may just be doing this in an idiotic manner, but i am trying to call a variable that is outside a for loop called $CSV which is an imported CSV file.
I think i just have the syntax wrong. I know when you are referring to a variable outside sometimes need to refer to the variable as $Name or $.Name depending. But neither of those seem to work.
Write-Host "Start Process"
$E3Stage2= New-MsolLicenseOptions -AccountSkuId EnvistaForensics:ENTERPRISEPACK -DisabledPlans BPOS_S_TODO_2, FORMS_PLAN_E3, STREAM_O365_E3, Deskless, FLOW_O365_P2, POWERAPPS_O365_P2, TEAMS1, PROJECTWORKMANAGEMENT, SWAY, INTUNE_O365, YAMMER_ENTERPRISE, MCOSTANDARD
$CSV = Import-csv "Input.csv"
$CSVStat = $CSV | Measure-Object
For ( $i=0; $i -lt $CSVStat.Count; $i++) {
Write-Host "Assigning License for user ---- $CSV.EmailAddress[$i]"
Set-MsolUserLicense -UserPrincipalName $CSV.EmailAddress[$i] -LicenseOptions $E3Stage2
}
Write-Progress "waiting six minutes" -Status "Please Wait"
Start-Sleep 360
For ( $i=0; $i -lt $CSVStat.Count; $i++) {
Write-Host "enabling litigation hold for user ---- $CSV.EmailAddress[$i]"
Set-Mailbox -identity $CSV.EmailAddress[$i] -LitigationHoldEnabled $true
}
Write-Host "Final User done script completed"
in the for loop the it cannot find $CSV and error out.
You are overcomplicating things:
$Csv already has a .count property, no need to Measure-Object
The for could be replaced by a
$csv | ForEach-Object {$_.EmailAddress}
or
ForeEach ($Row in $Csv){ $Row.EmailAddress}
To expand $Variable.Property inside a string use "$($Variable.Property)"

Shared and unique lines from large files. Fastest method?

This code returns unique and shared lines between two files. Unfortunately, it runs forever if the files have 1 million lines. Is there a faster way to do this (e.g., -eq, -match, wildcard, Compare-Object) or containment operators are the optimal approach?
$afile = Get-Content (Read-Host "Enter 'A' file")
$bfile = Get-Content (Read-Host "Enter 'B' file")
$afile |
? { $bfile -notcontains $_ } |
Set-Content lines_ONLY_in_A.txt
$bfile |
? { $afile -notcontains $_ } |
Set-Content lines_ONLY_in_B.txt
$afile |
? { $bfile -contains $_ } |
Set-Content lines_in_BOTH_A_and_B.txt
As mentioned in my answer to a previous question of yours, -contains is a slow operation, particularly with large arrays.
For exact matches you could use Compare-Object and discriminate the output by side indicator:
Compare-Object $afile $bfile -IncludeEqual | ForEach-Object {
switch ($_.SideIndicator) {
'<=' { $_.InputObject | Add-Content 'lines_ONLY_in_A.txt' }
'=>' { $_.InputObject | Add-Content 'lines_ONLY_in_B.txt' }
'==' { $_.InputObject | Add-Content 'lines_in_BOTH_A_and_B.txt' }
}
}
If that's still too slow try reading each file into a hashtable:
$afile = Get-Content (Read-Host "Enter 'A' file")
$ahash = #{}
$afile | ForEach-Object {
$ahash[$_] = $true
}
and process the files like this:
$afile | Where-Object {
-not $bhash.ContainsKey($_)
} | Set-Content 'lines_ONLY_in_A.txt'
If that still doesn't help you need to identify the bottleneck (reading the files, comparing the data, doing multiple comparisons, ...) and proceed from there.
try this:
$All=#()
$All+= Get-Content "c:\temp\a.txt" | %{[pscustomobject]#{Row=$_;File="A"}}
$All+= Get-Content "c:\temp\b.txt" | %{[pscustomobject]#{Row=$_;File="B"}}
$All | group row | %{
$InA=$_.Group.File.Contains("A")
$InB=$_.Group.File.Contains("B")
if ($InA -and $InB)
{
$_.Group.Row | select -unique | Out-File c:\temp\lines_in_A_And_B.txt -Append
}
elseif ($InA)
{
$_.Group.Row | select -unique | Out-File c:\temp\lines_Only_A.txt -Append
}
else
{
$_.Group.Row | select -unique | Out-File c:\temp\lines_Only_B.txt -Append
}
}
Full code for the best option (#ansgar-wiechers). A unique, B unique, and A,B shared lines:
$afile = Get-Content (Read-Host "Enter 'A' file")
$ahash = #{}
$afile | ForEach-Object {
$ahash[$_] = $true
}
$bfile = Get-Content (Read-Host "Enter 'B' file")
$bhash = #{}
$bfile | ForEach-Object {
$bhash[$_] = $true
}
$afile | Where-Object {
-not $bhash.ContainsKey($_)
} | Set-Content 'lines_ONLY_in_A.txt'
$bfile | Where-Object {
-not $ahash.ContainsKey($_)
} | Set-Content 'lines_ONLY_in_B.txt'
$afile | Where-Object {
$bhash.ContainsKey($_)
} | Set-Content 'lines_in _BOTH_A_and_B.txt'
Considering my suggestion to do a binary search, I have created a reusable Search-SortedArray function for this:
Description
The Search-SortedArray (alias Search) (binary) searches a string in a sorted array. If the string is found, the index of the found string in the array is returned. Otherwise, if the string is not found, a $Null is returned.
Function Search-SortedArray ([String[]]$SortedArray, [String]$Find, [Switch]$CaseSensitive) {
$l = 0; $r = $SortedArray.Count - 1
While ($l -le $r) {
$m = [int](($l + $r) / 2)
Switch ([String]::Compare($find, $SortedArray[$m], !$CaseSensitive)) {
-1 {$r = $m - 1}
1 {$l = $m + 1}
Default {Return $m}
}
}
}; Set-Alias Search Search-SortedArray
$afile |
? {(Search $bfile $_) -eq $Null} |
Set-Content lines_ONLY_in_A.txt
$bfile |
? {(Search $afile $_) -eq $Null} |
Set-Content lines_ONLY_in_B.txt
$afile |
? {(Search $bfile $_) -ne $Null} |
Set-Content lines_in_BOTH_A_and_B.txt
Note 1: Due to the overhead, a binary search will only give advantage with (very) large arrays.
Note 2: The array has to be sorted otherwise the result will be unpredictable.
Nate 3: The search doesn't account for duplicates. In case of duplicate values, just one index will be returned (which isn't a concern for this specific question)
Added 2017-11-07 based on the comment from #Ansgar Wiechers:
Quick benchmark with 2 files with a couple thousand lines each (including duplicate lines): binary search: 2400ms; compare-object: 1850ms; hashtable lookup: 250ms
The idea is that the binary search will take its advantage on the long run: the larger the arrays the more it will proportional gain performance.
Taken $afile |? { $bfile -notcontains $_ } as an example, the performance measurements in the comment and that “a couple thousand lines” is 3000 lines:
For a standard search, you will need an average of 1500 iterations in the $bfile:*1
(3000 + 1) / 2 = 3001 / 2 = 1500
For a binary search, you will need an average of 6.27 iterations in the $bfile:
(log2 3000 + 1) / 2 = (11.55 + 1) / 2 = 6.27
In both situations you do this 3000 times (for each item in $afile)
This means that each single iteration takes:
For a standard search: 250ms / 1500 / 3000 = 56 nanoseconds
For a binary search: 2400ms / 6.27 / 3000 = 127482 nanoseconds
The breakeven point will at about:
56 * ((x + 1) / 2 * 3000) = 127482 * ((log2 x + 1) / 2 * 3000)
Which is (according my calculations) at about 40000 entries.
*1 presuming that a hashtable lookup doesn’t do a binary search itself as it is unaware that the array is sorted
Added 2017-11-07
Conclusion from the comments: Hash tables appear to have a similar associative array algorithms that can't be outperformed with low-level programming commands.

Move X files from one folder to another in Powershell

I have a folder A with 75000 files which are to be processed. I have 4 folders (A,B,C,D) alongside it which can process 3000 files at a time.
I want a script to take 3000 files from A and put it in B. It should then take another 3000 files and put in C, then D and finally E
Below is the code I have so far. this takes 10 files and moves them into B, but then it just sits forever without putting any files into C,D or E.
Is there a way to quit out of the EnumerateFiles section of code? I just want the first X files it finds to get moved, I don't care about how many files are in A.
Any idea?
$dirBase = "\\networkDir\A\"
$dirProc1 = "\\networkDir\B\"
$dirProc2 = "\\networkDir\C\"
$dirProc3 = "\\networkDir\D\"
$dirProc4 = "\\networkDir\E\"
cd $dirBase
$directoryInfo1 = Get-ChildItem $dirProc1 | Measure-Object
$directoryInfo2 = Get-ChildItem $dirProc2 | Measure-Object
$directoryInfo3 = Get-ChildItem $dirProc3 | Measure-Object
$directoryInfo4 = Get-ChildItem $dirProc4 | Measure-Object
if ($directoryInfo1.count -eq 0) {
MoveFiles $dirBase $dirProc1
}
if ($directoryInfo2.count -eq 0) {
MoveFiles $dirBase $dirProc2
}
if ($directoryInfo3.count -eq 0) {
MoveFiles $dirBase $dirProc3
}
if ($directoryInfo4.count -eq 0) {
MoveFiles $dirBase $dirProc4
}
function MoveFiles([string]$srcDir, [string]$dest)
{
$FileLimit = 10
$Counter = 0
[IO.Directory]::EnumerateFiles($srcDir) | Where-Object {$Counter -lt $FileLimit} | %{
#Get-ChildItem $srcDir | Select-Object -first $FileLimit | %{
Move-Item $_ -destination $dest
$Counter++
}
}
Get-ChildItem $dirProc1 | select -first 3000
?

Active Directory and Powershell

I'm currently rewriting a script that is in VB into a Powershell script.
What the script does is search our Active Directory for a user based on the script-users input.
Function PromptForName{
$nameInput = "*"
$nameInput += Read-Host ("Please enter a full or partial name.")
$nameInput += "*"
return $nameInput
}
Function FindUsers{
param ([string]$n)
$usersArray = Get-ADUser -f {DisplayName -like $n} | Select-Object Name
return $usersArray
}
This code will print out the correct list of names. What I then want to do is allow the user to choose one of those names, and have more information about that person. I'm stuck at allowing the script-user to select one of those names.
How can I prompt for another input; where the box will display a numbered list of all the names that FindUsers gave, and then return a number based on which user they chose? I'm completely lost.
This is currently how I am trying to do it, although I'm pretty sure it's completely wrong.
Function PrintUsers{
param $a
[int]$i, $j
[string]$userList
$j = 1
foreach($object in $array){
$userList += ($j + $array[$i])
$j++
}
return $userList
}
Function SelectUser{
param $list
$user = Read-Host ($list)
}
EDIT:
I have updated my functions to the following:
Function FindUsers{
param ([string]$n)
$usersArray = #(Get-ADUser -f {DisplayName -like $n} | Select-Object Name| Format-List)
return $usersArray
}
Function PrintUsers{
param ([String[]]$array)
$i
for($i = 1; $i -lt $usersArray.length; $i++){
Write-Host "$i. $($usersArray[$i-1].Name)"
}
}
The output after FindUsers is like this:
Name : xxxxx yyyyy
Name : xxxxx zzzzz
etc.
So the return of $usersArray is printing all that.
I don't want any printing until the PrintUsers function, and I want it to be in a numbered list type format like this:
1. xxxx yyyy
2. xxxx zzzz
etc.
I'm having the most difficult time figuring this out.
# get all users
$usersArray = #(Get-ADUser -f {DisplayName -like $n} )
# create menu
for($i=1; $i -le $usersArray.count; $i++){
Write-Host "$i. $($usersArray[$i-1].Name)"
}
# prompt for user number
$user = Read-Host Enter the user number to get more info
# display full info for selected user
$usersArray[$user-1] | Format-List *
Use Add-Member to add a unique identifier to each user. Let's treat processes as if they're user objects, for the sake of example:
$procs = gps;
$procs = $procs | % { $i=0; } {
Add-Member -MemberType NoteProperty -InputObject $_ -Name Number -Value $i -PassThru;
$i++;
};
$procs | select Number,Name;
$procid = Read-Host -Prompt 'Enter the number of the process you would like to operate on';
$proc = $procs | ? { $_.Number -eq $procid };
Write-Host -Object ('Operating on proc: {0}' -f $proc.Name);

Resources