I have a need to install multiple printers to multiple servers and was wanting to script this so that it would only need to be configured once.
I am using PowerShell to query 2 CSV files; 1 to get a list of computers to install printers too and the other to list the Name, IP, Driver, Location of the needed printers. I am using 2 foreach loops to accomplish this and a break to try to get it to loop correctly.
Currently with the break :outer where it is the first PC gets all printers installed. If I move it to inside the foreach ($printer in $printers) loop it will only install the first printer to all computers.
Thank you for any assistance that anyone can provide.
$computerfile = Import-Csv C:\computerlist.csv
$printers = Import-Csv C:\printers2.csv
foreach ($computer in $computerfile) {
"computer: $computer"
:outer
foreach ($printer in $printers) {
Add-PrinterPort -ComputerName $computer.Name -Name $printer.IP -PrinterHostAddress $printer.IP
Add-Printer -ComputerName $computer.Name -Name $printer.Name -DriverName $printer.Driver -PortName $printer.IP -ShareName $printer.Name -Location $printer.Location
Set-printer -ComputerName $computer.Name -Name $printer.Name -Shared $false -Published $true
}
break :outer
}
Please remove the break :outer from your code.
What is happening is that the first loop starts running, and enters the second loop only once! because of the break, and jumps to the next computer.
Related
I try to uninstall a msi file, but when I try this via array I get an error (cant find installation package)
When I do the same but not in array - it works
for ($i=0; $i -lt $msiArrayClean.length; $i++){
Write-Host $msiArrayClean[$i]
& msiexec.exe /x $msiArrayClean[$i]
}
here the output of Write Host
How i come to $msiArrayClean
$msiCache = get-wmiobject Win32_Product | Where-Object Name -like "*7-Zip*" | Format-Table LocalPackage -AutoSize -HideTableHeaders
$msiString = $msiCache | Out-String
$msiArrayWithEmptyLines = $msiString -split "`n"
$msiArray = $msiArrayWithEmptyLines.Split('', [System.StringSplitOptions]::RemoveEmptyEntries)
$msiArrayCleanString = $msiArray | Out-String
$msiArrayClean = $msiArrayCleanString -split "`n"
A few caveats up front:
Format-* cmdlets output objects whose sole purpose is to provide formatting instructions to PowerShell's output-formatting system - see this answer. In short: only ever use Format-* cmdlets to format data for display, never for subsequent programmatic processing.
The CIM cmdlets (e.g., Get-CimInstance) superseded the WMI cmdlets (e.g., Get-WmiObject) in PowerShell v3 (released in September 2012). Therefore, the WMI cmdlets should be avoided, not least because PowerShell (Core) (version 6 and above), where all future effort will go, doesn't even have them anymore. For more information, see this answer.
Use of the Win32_Product WMI class is discouraged, both for reasons of performance and due to potentially unwanted side effects - see this Microsoft article.
An alternative - available in Windows PowerShell only (not in PowerShell (Core) 7+) - is to use the following to get uninstall command lines and execute them via cmd /c:
Get-Package -ProviderName Programs -IncludeWindowsInstaller |
ForEach-Object { $_.meta.attributes['UninstallString'] }
If you need to stick with Win32_Product:
# Get the MSI package paths of all installed products, where defined.
$msiCache = (Get-CimInstance Win32_Product).LocalPackage -ne $null
foreach ($msiPackagePath in $msiCache) {
if (Test-Path -LiteralPath $msiPackagePath) {
# Note that msiexec.exe runs *asynchronously*.
# Use Start-Process -Wait to wait for each call to complete.
& msiexec.exe /x $msiPackagePath
} else {
Write-Warning "Package not found: $msiPackagePath"
}
}
I don't like reaching to WMI, since its perfomance is the issue. I prefer to do it via registry and it worked for me many times. Code explanation in comments.
$name = "7-zip"
#Get all items from registry
foreach ($obj in Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall") {
#Get DisplayName property of registry
$dname = $obj.GetValue("DisplayName")
#Search for given name
if ($dname -like "*$name*") {
#Get uninstall string (it gets you msiexec /I{APPID})
$uninstString = $obj.GetValue("UninstallString")
foreach ($line in $uninstString) {
#Getting GUID from "{" to "}""
$found = $line -match '(\{.+\}).*'
if ($found) {
#If found - get GUID
$appid = $matches[1]
Write-Output "About to uninstall app $appid"
#Start uninstallation
Start-Process "msiexec.exe" -arg "/X $appid /qb" -Wait
}
}
}
}
Edit: Added solution with msi path after Nehat's comment as this works for me (I tried to minimize the code :))
$msiCache = get-wmiobject Win32_Product | Where-Object Name -like "*7-Zip*" | Format-Table LocalPackage -AutoSize -HideTableHeaders
foreach ($msi in $msiCache | Out-String) {
if ([string]::IsNullOrEmpty($msi)) {
continue
}
Write-Host $msi
Start-Process "msiexec.exe" -arg "/x $msi" -Wait
}
I'm back!
Anyway, I'm running an exchange script to find emails that contain a specific list of keywords for a specific set of users, defined as users and terms in the script below, and it works. However after about an hour or so of running, it's consuming obnoxious amounts of memory, 12 - 14 GB. and running very slowly.
It does flux between 3 GB and 14 GB, so I don't know if this is simply normal, expected behavior or if its' something wrong with my script. I am aware that I'm using a sorta(?) depreciated commandlet in the search-mailbox function, but I'm only searching about 300 users and 21 terms, so I don't think I need to use the new-mailboxsearch commandlet.
Script for Reference
$users = Get-Content x:\xxx\xxx\users.txt
$terms = Get-Content x:\xxx\xxx\Terms.txt
ForEach ($term in $Terms) {
ForEach ($line in $users) {
$Results = Search-Mailbox -Identity $line -SearchQuery $term -TargetMailbox SearchResults2 -TargetFolder $term -LogLevel Full | Select Identity,TargetFolder,ResultItemsCount
Add-Content -Path x:\xxx\xxx\outputfile.txt -Value "$($term);$($line);$($Results.TargetFolder);$($Results.ResultItemsCount)"
}
}
Anyway, any help is, as always, greatly appreciated.
Thanks!
Does foreach-object fair better?
$terms | ForEach { $term = $_
$users | ForEach { $line = $_
..
}
}
The problem wasn’t with the script itself, it was the environment we were running it in.
For some reason running the script inside of the integrated scripting environment Powershell ISE, was causing the script to suck up crazy amounts of memory, eventually halting the system. By simply launching it outside of the ISE we were able to get the script to behave normally:
Thanks to everyone who replied!
I am new to Powershell and I am having an issue within a loop that I need assistance with. I am attempting to rename some files that are created as part of the process within the loop.
I have tested the code OUTSIDE of the loop and it works fine. However, when I try to put it in the loop, nothing seems to happen.
The files I need to rename are in the following locations…
(1)“\MYSERVER\MYCOMPANY\MYFOLDER\ MyPrintouts\EstimateImport\ImportPrintout.txt”
(2)“\MYSERVER\MYCOMPANY\MYFOLDER\ MyPrintouts\PostEntries\ImportPostEntries.txt”
I need to tack the date and time on the end. This code works for me OUTSIDE of the loop. I put it in a file I named RenameFiles.ps1
#File location
$ImportPrintout = “\\MYSERVER\MYCOMPANY\MYFOLDER\MyPrintouts\EstimateImport\ImportPrintout.txt”
$ImportPostEntries = “\MYSERVER\MYCOMPANY\MYFOLDER\ MyPrintouts\PostEntries\ImportPostEntries.txt”
#Find and rename the import printouts
Get-ChildItem $ImportPrintout -Filter "ImportPrintout.txt" | ForEach-Object {
Rename-Item $_.FullName "$BackupFolder$($_.BaseName -replace " ", "_" -replace '\..*?$')$(Get-Date -Format "MMddyyyy-HHmmss").txt"}
Get-ChildItem $ImportPostEntries -Filter "ImportPostEntires.txt" | ForEach-Object {
Rename-Item $_.FullName "$BackupFolder$($_.BaseName -replace " ", "_" -replace '\..*?$')$(Get-Date -Format "MMddyyyy-HHmmss").txt"}
This is how I added it to the loop as I want the files renamed BEFORE the next file is processed…
#Define actions after an Event is Detected
$action = {$files = Get-ChildItem -Path $watcher.Path -Filter $watcher.Filter #-Recurse
foreach ($file in $files)
{
#Define variables for log file
$changeType = $Event.SourceEventArgs.ChangeType #Your event trigger "Created"
$fileDate = $file.LastWriteTime #Date/Time Estimate was created
#logline contains = Date/Time of import, Created, Date/Time Created, filename
$logline = "$(Get-Date), $changeType, $fileDate, $file"
#Actions to take ==============================================================
#Write details to the log
Add-Content "“\\MYSERVER\MYCOMPANY\MYFOLDER\EstimateImportLog.txt" -value $logline
#Copy the estimate to the "ToBeProcessed" folder
Copy-Item $file.FullName -Destination $copyTo
#Move the estimate to the "EstimateHistory" folder
Move-Item $file.FullName -Destination $moveTo -Force
#Run the powershell script that launches the .bat file that launches the macro
Invoke-Expression "& '\\MYSERVER\MYCOMPANY\MYFOLDER\PSscriptToRunBATfile.ps1'"
#Pause the script for 30 seconds to allow the estimate to finish posting
Start-Sleep -Seconds 30
Invoke-Expression "& '“\\MYSERVER\MYCOMPANY\MYFOLDER\RenameFiles.ps1'"
}
}
This seems to “break” my loop. However, I need this to be done BEFORE going to the next file. How can I accomplish renaming these files before moving on. Any assistance would be greatly appreciated. Thank you!
As far as the loop failing, you're probably encountering an error. Either set your $ErrorActionPreference to Continue or set it to Stop and wrap try/catch blocks around your copy-item and move-item to detect and handle errors.
That probably also addresses the failure of the copy-item/move-item to change the file name, it's running into an error trying to perform that action and failing.
This is my first post, I've been a long time reader of various post.
What I have to do is disable a large number of services in a large number of VM's. I have 6-7 different enviornments to do this in I created 2 text files//paths which I gave the variables $vmList and $serviceList.
When I run these through the different environments using (EXTERNAL IP) addresses it works in for only 1 environment. When I try to run this with (INTERNAL IP) addresses I get the same identical exception thrown which is that it cannot find the set service (defragsvc) on the machine and then exits the loops. However when I log into these machines they are definitely there but not started. I am definitely lost as to why this works in 1 environment (confirmed on the machines) but fails in the others any help would be appreciated
$vmList =gc C:\PowerCli\Services\Staging_1_Defrag_Kill.txt
$serviceList =gc C:\PowerCli\Services\ServicesKill.txt
ForEach ($vm in $vmList){
ForEach ($service in $serviceList){
write-host $vm
Stop-service -inputobject(Get-Service -Name $service -ComputerName $vm)-force -confirm:$false -EA SilentlyContinue
Set-Service -ComputerName $vm -Name $service -EA Stop -Startmode Disabled
Write-Host "$vm : Successfully disabled the service $service" -ErrorAction SilentlyContinue
}
}
Hah, I figured it out,
As it turns out, I couldnt connect to the service control manager, it was a permissions error not a problem with the script. Lessons learned
Here is the story. I have a fileshare that is replicated between 2 servers located in different places in the world. DFS will not replicate a file if it has only been viewed, but I wouldn't want to delete that file/folder because it was used within the time period I have set (7 days). So to make sure that I don't remove still used files I have to check both locations for their LastAccessTime.
I currently have this
Set-ExecutionPolicy RemoteSigned
$limit = (Get-Date).AddDays(-7)
$PathOne = "FirstPath"
$PathTwo = "SecondPath"
$ToBeDeletedPathOne = Get-ChildItem -Path $PathOne -Recurse -Force | Where-Object { !$_.PSIsContainer -and $_.LastAccessTime -lt $limit }
$TobeDeletedPathTwo = Get-ChildItem -Path $PathTwo -Recurse -Force | Where-Object { !$_.PSIsContainer -and $_.LastAccessTime -lt $limit }
$DiffObjects = Compare-Object -referenceobject $ToBeDeletedPathOne -differenceobject $ToBeDeletedPathTwo -IncludeEqual
$ToBeDeletedOverall = DiffObjects | where {$_.SideIndicator -eq "=="}
After this, I loop through and delete the files that are marked for deletion by both locations.
Part of the problem I have is that there are a tremendous amount of files and this can take a very long time. So I wanted to make it better/faster. My idea is to have this script run the scan as a different script on each FS server and wait for them to return the output. That way it can scan on the local machine easier than remotely and it would scan both locations simultaneously.
The other part of the problem comes in with the fact that I have no idea how to do this. I will continue to work on this and if I solve it, I will post here in case anyone in the future finds this useful.
You could run everything locally. Copy the script to the machines you want (make a script to copy them if you need to) then use something like PSTools to kick them off on the local machines. This should essentially run the script simultaneously on all machines.