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)"
Related
For example, you specified a variable as shown below.
$data1 = get-psdrive | where-object {$_.name -like 'c'} | select -expandproperty used
$data2 = get-psdrive | where-object {$_.name -like 'c'} | select -expandproperty free
echo $data1,data2
The output is vertical.
$data1
$data2
I used write-host -nonewline to display output horizontally, but the command does not export to txt
write-host $data1 -nonewline; write-host $data2 -nonewline >> c:\test.txt
How can I display horizontally and export in txt?
How about converting to json (or csv)? Note that ">>" can mix encodings, but add-content doesn't.
get-psdrive c | select used, free | ConvertTo-Json -Compress |
add-content test.txt
get-content test.txt
{"Used":217365741568,"Free":21004943360}
Or just join them. Too bad select -expand doesn't work with multiple properties.
psdrive c | % { ($_.used,$_.free) -join ',' }
217382371328,20988313600
You can't use Write-Host for this. Even if you change to Write-Host -NoNewLine it'll still never work because Write-Host is intended for directly writing into the screen and can't be redirected unless you use PowerShell 5+ and redirect the stream number 6 (Information stream)
Write-Host -NoNewLine $data1,$data2 6>output.txt
in which case it doesn't print out to string of course. In short Write-Host in PowerShell 5+ doesn't write to stdout but the Information stream
The real solution to writing to screen with the ability to redirect to file or pipe to another command is to use Write-Output (which echo is an alias to), and simply use a single string to write to a single line
Write-Output "$data1,$data2"
echo "$data1,$data2"
echo ($data1 + "," + $data2)
See Write-Host Considered Harmful for more details
Use a double quote string with your variables: Write-Host “$data1,$data2”
Read more https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_quoting_rules?view=powershell-7.1
Hi I have been trying to find a way to help me estimate how long it will take to move databases from one location to another. My online research has helped me through a few issues so far but I seem to be stuck because I have it using what seems to be the correct commands to see all files that would need to be counted but it comes back on a 5TB database as it will only take 22 milliseconds so either I have a faster network and server that I even knew or I screwed this up some how that I cannot see.
$item = get-childitem 'D:\SQL01' -Recurse
$d = "E:\SQL01"
$results = #()
$results = Foreach ($i in $item) {
Measure-Command -Expression {
Copy-Item -literalpath $i $d
}
}
($results | Measure-Object -Property TotalSeconds -Sum).Sum
$results -f "c"
Reading over this it seems fine and it even returns a the sum of time but there is no way that is accurate. Please leave a comment if anyone sees where I did something wrong or you think there is something I could try differently.
Here is an example of Write-Progress in action:
# Get all directories on D:\SQL01 recursively
$directories = Get-ChildItem 'D:\SQL01' -Directory -Recurse
# Set a destination folder
$destination = 'E:\SQL01'
$dirCount = $directories.count
$i=0;foreach($directory in $directories)
{
$progress = #{
Activity = "Copying - {0}" -f $directory.FullName
Status = "Folder $i of $dirCount"
PercentComplete = $i++ / $dirCount * 100
}
Write-Progress #progress
Copy-Item -Path $directory -Destination $destination -Recurse
}
In the script below I get it run fine, however once an error occurrs it fails to continue where it left off before the error, I am a little confused as to what I am missing on this, so any help would be great.
First off, it loads a text file that contains a list of distribution groups, this then queries this list with exchange to get the exact match displayname and then cycles through a CSV adding each user to the distribution list, this all works fine, it's when an error occurrs that is the issue.
the catch statement understands the error and then carries out the tasks, once the task is complete it runs the script from the forloop in the catch statement and not in the try.
I have added the function UpdateDistros
To allow the script to run again, but the problem is it rus through the entire text file again instead of starting at where the error occurred, even if its at line 1 or line 25.
Below is the code;
## ##
## Adds users from text file to distributon lists ##
## ##
######################################################
Import-Module ExchangeOnlineManagement
Connect-ExchangeOnline -UserPrincipalName 'UserPrincipalName'
Function UpdateDistros{
$ErroractionPreference = 'Stop'
$Path = "C:\INSTALLS\Exchange-Distro_Lists\Data"
$res = $Path+"\Results"
$txt1 = "Distros.txt"
$txt2 = "Results.txt"
$txt3 = "Mail_Contacts.txt"
$txt4 = "Errors.txt"
$DS1 = gc $Path"\"$txt1 # Gets information from a text file
$1 = "Error Occurred"
$R1 = $res+"\"+$txt2
$R2 = $res+"\"+$txt3
$R3 = $res+"\"+$txt4
try{
Foreach($DS in $DS1){ # Cycles through text file
$DSG = Get-DistributionGroup -identity $DS | Select * -ExpandProperty Alias # Gets display name for distribution group
$DS2 = $path+ "\"+ "$DSG.csv"
$NUS = Import-csv $DS2
#Foreach { $_ + '#zenith.co.uk' } | ` # ammends all users in each text file to contain #zenith.co.uk
#Foreach { $_ –replace “ “,”.” } # ammends all users in each text file to contain #zenith.co.uk
Foreach($NU in $NUS){ # Cycles through each record in each text file
$N1 = $NU.DP
$N2 = $NU.EM
#$NU
Add-DistributionGroupMember -identity $DS -Member $N1 #Adds each record to the correct distribution List
"$N1 added to $DS" | Out-file $R1 -Append
Write-host "Completed Script"
}
}
}
Catch{
$Erw = $_.Exception.Message
$Erw = $Erw -replace """",""
$NMC = get-content $DS2
$Rers = "Couldn't find object $N1. Please make sure that it was spelled correctly or specify a different object."
$Erw
$Rers
$A1 = $N1 -replace "\s",""
if($Erw -contains $Rers){
Foreach($NM in $NMC){
Write-host $1
Write-host $Erw
$Unq = 'There are multiple recipients matching identity "$N1". Please specify a unique value.'
$ERM = $_.Exception.Message
if($ERM -contains $Unq){
$NU = $NU + '- Cartwright'
New-MailContact -Name $N1 -ExternalEmailAddress $N2 -Alias $A1
"New Mail contact created for $N1" | Out-file $R2 -Append -ErrorAction continue
UpdateDistros
}
ElseIf($Erw -contains $Rers){
New-MailContact -Name $N1 -ExternalEmailAddress $N2 -Alias $A1
"New Mail contact created for $N1" | Out-file $R2 -Append -ErrorAction continue
UpdateDistros
}
Else{
$Erw | Out-file $R3 -Append -ErrorAction continue
UpdateDistros
}
}
}
Else{
$Erw | Out-file $R3 -Append -ErrorAction continue }
UpdateDistros
}
}
UpdateDistros
You should not put the entire body of your script in a try/catch block. Doing this makes it really hard to tell where the error occured and how to deal with it.
First, delete the try/catch blocks you have.
Next, add one specifically for the issue you want to deal with. You said that you the script fails to continue when it can't add a user to a distribution group, making that a good place to add handling.
Also, I'm moving the comment to the top of the line, instead of the end.
Full lines of text are hard to read, but relatively short columns can be scanned easily.
try{
#Adds each record to the correct distribution List
Add-DistributionGroupMember -identity $DS -Member $N1
}
catch{
$message = "Failed to modify group $($DS) with new member $($N1)"
Write-warning $message
$message | Add-content C:\pathTo\SomeLogfile.txt
CONTINUE
}
The Continue command in the end is a special PowerShell flow control command.
It means 'Stop processing this item in our current loop and move on to the next one`.
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.
Very very much a PowerShell newbie here I wanted a script to scan devices on the network and report on Local Admins. Found one out there and made some minor modifications to meet my needs - but I have one mod I cant work out how to do. Hoping someone out there will know a simple way to do it ?
The scrip below will read in a list of device names - scan them and output a dated report for all devices that are live and on-line. If the device is not accessible I get the following error on screen but nothing in the report.
I would like when it encounters an error that it writes to the report file - something along the lines of "$computor was not accessible!"
The code I am using is
$date = Get-Date -Format o | foreach {$_ -replace ":", "."}
ECHO "Starting scan"
$Result = #()
foreach($server in (gc .\servers.txt)){
$computer = [ADSI](”WinNT://” + $server + “,computer”)
$Group = $computer.psbase.children.find(”Administrators”)
$Filename = "c:\" + "LocalAdminAudit" + $date + ".txt"
function getAdmins
{
ECHO "SEARCHING FOR DEVICE"
$members = ($Group.psbase.invoke(”Members”) | %
{$_.GetType().InvokeMember(”Adspath”, ‘GetProperty’, $null, $_, $null)}) -
replace ('WinNT://DOMAIN/' + $server + '/'), '' -replace ('WinNT://DOMAIN/',
'DOMAIN\') -replace ('WinNT://', '')
$members}
ECHO "READY TO WRITE OUTPUT"
$Result += Write-Output "SERVER: $server"
$Result += Write-Output ' '
$Result += ( getAdmins )
$Result += Write-Output '____________________________'
$Result += Write-Output ' '
ECHO "Record written"
}
# Added date run to report
$result += Write-Output "Date Reported: $date"
$Result > $Filename
Invoke-Item $Filename
# replace "DOMAIN" with the domain name.
ECHO "Scan Complete"
And the on screen error when a machine is off line or otherwise doesn't respond is
Exception calling "Find" with "1" argument(s): "The network path was not found.
"
At \server\users\User.Name\Powershell Scripts\Get-Local-AdminsV3.ps1:1
0 char:40
+ $Group = $computer.psbase.children.find <<<< (”Administrators”)
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException
I would like when it encounters an error that it writes to the report file - something along the lines of "$computor was not accessible!" - I am pretty sure there must be an easy way of doing this - but I cant work it out so any tips would be greatly appreciated
As Matt, mentioned in the comments. You can use a Try/Catch block inside your function to catch the error.
I also made some other changes. The most major is that I changed the function to contain all of the code necessary to get the local administrator group. Then the loop just calls the function once per computer with the computer name. This function is then reusable.
Secondly rather than output to a text file, I changed to outputting to a CSV as is a more structured format that can be used better later.
Also rather than relying on writing to the console host, I used Write-Progress to report the progress of the loop.
$Servers = Get-Content .\servers.txt
$ExportFileName = "c:\LocalAdminAudit$date.csv"
function Get-LocalAdministrator {
[cmdletbinding()]
Param(
$ComputerName
)
$Group = [ADSI]("WinNT://$computername/Administrators,group")
try {
$Group.Invoke("Members") | ForEach-Object {
$User = ($_.GetType().InvokeMember("Adspath", 'GetProperty', $null, $_, $null) -split '/')[-2,-1] -join '\'
[PSCustomObject]#{
"User" = $User
"Server" = $ComputerName
"Date" = Get-Date -Format o | ForEach-Object {$_ -replace ":", "."}
}
}
}
catch {
[PSCustomObject]#{
"User" = "Failed to Report"
"Server" = $ComputerName
"Date" = Get-Date -Format o | ForEach-Object {$_ -replace ":", "."}
}
}
}
$LocalAdmins = foreach ($Server in $Servers) {
Write-Progress -Activity "Retrieving Local Administrators" -Status "Checking $Server" -PercentComplete (([array]::indexof($Servers,$Server)/($Server.count))*100)
Get-LocalAdministrator $Server
}
$LocalAdmins | Export-CSV $ExportFileName -NoTypeInformation
Invoke-Item $ExportFileName
Lastly, be careful of smart quotes especially when cutting and pasting between Outlook and word.