Why is this foreach statement not looping? - powershell

I've used this setup with a foreach statement multiple times and never had any issues. It points to a text file with multiple lines of input. However, this script only outputs the hard disk information for the first input line in the text file, and then stops. What's going on here?
# Create log file directory
New-Item -Path "C:\scripts\logs" -ItemType "directory" -ErrorAction SilentlyContinue
# Set script name
$ScriptName = "Hard_Disk_Count.ps1"
# Set script log file
$LogFile = "C:\scripts\logs\Hard_Disk_Count.log"
# Clear script log file
Clear-Content -Path "C:\scripts\logs\Hard_Disk_Count.log"
# Import VMware modules
Write-Host "Importing VMWare modules." -ForegroundColor Cyan
Import-Module VMware.VumAutomation
Import-Module VMware.VimAutomation.Core
Import-Module VMware.VimAutomation.Storage
Import-Module VMware.VimAutomation.Common
Import-Module VMware.VimAutomation.License
# Connect to a vCenter
$VIServer = "example"
Connect-VIServer -Server $VIServer
Try {
# Prompt for confirmation that input file is updated
$confirmation = Read-Host "Please confirm that the text file located at 'C:\scripts\input.txt' contains the correct information.`nEnter 'y' to proceed."
if ( $confirmation -eq 'y' ) {
$InputFile = Get-Content "C:\scripts\input.txt"
foreach( $I in $InputFile ){
# Example write to log file
Write-Host "Script $ScriptName started. Log file located at $LogFile." -ForegroundColor Cyan
"$(Get-Date) - Script $ScriptName started." >> $LogFile
$ClusterName = "$I"+"_Cluster"
$VM =
Get-Cluster $ClusterName |
Get-VM |
Where-Object { $_.Name -like "*190*" }
$Disks = Get-HardDisk -VM $VM
$DiskCount = $Disks.count
"$VM, $DiskCount" >> $LogFile
}
}
}
Catch {
# Error email message send
}

Related

Using Powershell to backup remote computers event logs

I'm very new to PowerShell and my end goal is to backup event logs on remote servers to a fileshare on the network. I was able to get my script working locally on a single server, backing up the servers event logs to a folder. Now, I'm trying to run this script from "server A" and backup event logs on "Server B" and "Server C". I was hoping I can accomplish this via simply creating a new ps session for each server and copy pasting my code. This of course wasn't the case. I'm guessing I might have to go through and add -Computer parameters to somethings?
I got the foundational code from this website
Was hoping a PS guru could point out my failures, here's my code:
$Servers = 'ServerB'
$ArchiveServer = 'ServerA' #For Testing
foreach ($Server in $Servers){
Enter-PSSession -ComputerName $Server -Credential mycred
$RemoteArchive = "\\" + $ArchiveServer +"\c$\z-TestScript-lastname"
$LocalArchive = "C:\z-TestScript"
$ArchiveComp = "\$Server"
$ArchiveFolder = "\Archive-" + (Get-Date -Format "yyyy-MM-dd") + "test14" ### Added test to get past testpath check
$RemoteLogFolder = "\\" + $Server + "\c$\Windows\System32\winevt\Logs"
$LogFolder = "C:\Windows\System32\winevt\Logs\*"
$RemoteCompressedPath = -join("$RemoteArchive","$ArchiveComp","$ArchiveFolder",".zip")
$CompressedPath = -join("$ArchiveLoc","$ArchiveComp","$ArchiveFolder",".zip")
$RemoteArchivePath = -join("$RemoteArchive","$ArchiveComp","$ArchiveFolder")
$ArchivePath = -join("$ArchiveLoc","$ArchiveComp","$ArchiveFolder")
$winlogs = 'application', 'security', 'system', 'setup', 'hardwareevents', 'internet explorer', 'key management service', 'oalerts', 'Parameters', 'oneapp_IGCC', 'windows powershell'
# Checking paths
If (!(Test-Path $RemoteArchive)) {
Write-Host
Write-Host "Archive folder $RemoteArchive does not exist, aborting ..." -ForegroundColor Red
Exit
}
If ((Test-Path $RemoteArchivePath)) {
Write-Host
Write-Host "Archive path $RemoteArchivePath exists, aborting ..." -ForegroundColor Red
Exit
}
If (!(Test-Path $RemoteArchivePath)) {
Write-Host
Write-Host "Creating Archive folder $RemoteArchivePath ..." -ForegroundColor Red
New-Item -Path $RemoteArchivePath -type directory -Force
}
# For all newer event logs, archive.
foreach ($winlog in $winlogs) {
# Configure environment
$sysName = $Server
$eventName = "$winlog Event Log Monitoring"
# Add event source to log if necessary
If (-NOT ([System.Diagnostics.EventLog]::SourceExists($eventName))) {
New-EventLog -ComputerName $Server -LogName $winlog -Source $eventName
}
# Check the log
if ( $winlog -ne 'Setup'){
$Log = Get-WmiObject Win32_NTEventLogFile | Where-Object {$_.logfilename -eq "$winlog"}
# Archive the log
$ArchiveFile = $ArchivePath + "\$winlog-" + (Get-Date -Format "yyyy-MM-dd#HHmm") + ".evt"
$EventMessage = "The $winlog event log will now be backed up."
$Results = ($Log.BackupEventlog($ArchiveFile)).ReturnValue
If ($Results -eq 0) {
# Successful backup of the event log
$Results = ($Log.ClearEventlog()).ReturnValue
$EventMessage += "The $winlog event log was successfully archived to $ArchiveFile and cleared."
Write-Host $EventMessage
Write-EventLog -LogName $winlog -Source $eventName -EventId 11 -EntryType Information -Message $eventMessage -Category 0
}
Else {
$EventMessage += "The #winlog event log could not be archived to $ArchiveFile and was not cleared. Review and resolve security event log issues on $sysName ASAP!"
Write-Host $EventMessage
Write-EventLog -LogName $winlog -Source $eventName -EventId 11 -EntryType Error -Message $eventMessage -Category 0
}
# Close the log
$Log.Dispose()
}
# For older archives like setup, archive.
Else {
$Log = Get-winevent -Listlog Setup | select Logname, Logfilepath | ForEach-Object -Process {
$name = $_.Logname
$safename = $name.Replace("/","-")
$logpath = $_.Logfilepath
$path = $ArchivePath + "\Setup" + (Get-Date -Format "yyyy-MM-dd#HHmm") + ".evt"
wevtutil.exe EPL $safename $path
Write-Host "Copying Setup log to: $path"
}
}
}
#Copy any windows archived logs over, then delete
Write-Host "Copying Archived Logs..."
Get-ChildItem -Path $LogFolder -Include Archive* -Recurse | Copy-Item -Destination $ArchivePath
Get-ChildItem -Path $LogFolder -Include Archive* -Recurse | Remove-Item
#Compress the folder
Write-Host "Compressing $ArchivePath and moving to $CompressedPath"
Compress-Archive -Path $ArchivePath -Destination $CompressedPath
Exit-PSSession
}
Main errors I'm getting as it goes through the foreach loop for the winlogs, it seems to run it twice, once for remote pc and once for local.

Deactivating all "ready/running Tasks" on a bunch of servers in Specific Folders with the ability to activate those tasks again

i am looking for a Powershell script that can disable all tasks that are "ready" or "running" in a specific folder in the Task Scheduler on 3 or more servers.
After we updated the software i should be able to activate all the tasks that were disabled by the script again, not just activate all disabled scripts, but specificly the ones that were disabled by the script.
I know this should be possible, but i am not capable of assembling the single parts. Everything thats more than a single command is to much for my logic capacitys.
<#getScheduledTasksinfo-FreeholdReboots.ps1
.Synopsis
PowerShell script to list all Scheduled Tasks, the User ID and the State
.DESCRIPTION
This script scans the content of the c:\Windows\System32\tasks and searches the UserID XML value.
The output of the script is a comma-separated log file containing the Computername, Task name, UserID, Status.
.Author
UNKNOWN
.Tweaker
Patrick Burwell
#>
Remove-Item -Force "D:\batch\Logs\Maintenance\$day-SchedTasks-Reboots.csv"
$logfilepath = "D:\batch\Logs\Maintenance\$day-SchedTasks-Reboots.csv"
$ErrorActionPreference = "SilentlyContinue"
$serverlist = gc D:\batch\input\servers.txt
foreach($server in $serverlist){
if($server -like '#*')
{
continue
}
Write-Host $server
$path = "\\" + $server + "\c$\Windows\System32\Tasks"
$tasks = Get-ChildItem -Path $path -File
if ($tasks)
{
Write-Verbose -Message "I found $($tasks.count) tasks for $server"
}
foreach ($item in $tasks)
{
if($item -like 'Optimize Start Menu*'){continue}
if ($item -like "User_Feed_Synchronization*"){continue}
if($item -like 'ComputeSensorWatchDog*'){continue}
if ($item -like "Google*"){continue}
$AbsolutePath = $path + "\" + $item.Name
$task = [xml] (Get-Content $AbsolutePath)
$states = (Get-ScheduledTask -Verbose -TaskPath '\' -TaskName "reboot")
[STRING]$check = $task.Task.Principals.Principal.UserId
[STRING]$state = $states.State
if ($task.Task.Principals.Principal.UserId)
{
if($item -ilike "*reboot*"){
Write-Verbose -Message "Writing the log file with values for $server"
Add-content -path $logfilepath -Value "$server,$item,$check,$state"
}
else {continue}
}
}
}

Add time in output file

This script add domain users to any other or remote domain computer / system's 'Administrators' group through the PowerShell.
This returns a final status in a csv with three columns (Computer name, availability, status)
I need to add a fourth column to this output file that contains the time and date.
#Create a file in the required path and update in the below command line
$Output = "C:\CSV\Output.csv"
#The output field of the computer will blank if the user is already exist in the group
Add-Content -Path $Output -Value "ComputerName,Availability,Status"
$status = $null
$availability = $null
#Save the CSV (Comma seperated) file with the server host name and the username to be added
Import-Csv C:\CSV\Computer.csv | ForEach-Object {
$Computer=$_.Computer
$User=$_.user
if (Test-Connection -ComputerName $Computer -Count 1 -Quiet) {
Write-Verbose "$Computer : Online"
$availability="Oniline"
try {
$GroupObj=[ADSI]"WinNT://$Computer/Administrators,group"
$GroupObj.PSBase.Invoke("Add",([ADSI]"WinNT://jdom.edu/$User").Path)
$status="Success"
#Update the status in the output file
Add-Content -Path $Output -Value ("{0},{1},{2}" -f $Computer, $availability, $status)
} catch {
Write-Verbose "Failed"
}
} else {
Write-Warning "$Computer : Offline"
$availability = "Offline"
$status = "failed"
#Update the status in the output file
Add-Content -Path $Output -Value ("{0},{1},{2}" -f $Computer, $availability, $status)
}
}
This is how the output file looks, this is where I want to add the fourth column with date and time:
ComputerName,Availability,Status
TD123696WJN339P,Oniline,Success
TD123419WJN339P,Oniline,Success
ComputerName,Availability,Status
5VERF9097LTIO01,Offline,failed
ZF001024DJH706G,Offline,failed
5MICF9017LTIO01,Offline,failed
The simple approach would be to just add another field to your output, i.e.
Add-Content -Path $Output -Value "ComputerName,Availability,Status,Timestamp"
and
"{0},{1},{2},{3}" -f $Computer, $availability, $status, (Get-Date)
However, unless you actually want multiple header lines in your output file (why?) you should rather use calculated properties and Export-Csv.
Import-Csv 'input.csv' |
Select-Object Computer, User, #{n='Status';e={
if (Test-Connection -ComputerName $_.Computer -Count 1 -Quiet) {
...
} else {
...
}
}}, #{n='Timestamp';e={Get-Date}} |
Export-Csv 'output.csv' -NoType
This is really interesting approach you have there working with CSV and it overcomplicates the scenario a bit (from my perspective and no disrespect!).
Why don't try using a PowerShell Custom Object?
#Create a file in the required path and update in the below command line
$Output = "C:\CSV\Output.csv"
#The output field of the computer will blank if the user is already exist in the group
Add-Content -Path $Output -Value "ComputerName,Availability,Status"
$status = $null
$availability = $null
#Save the CSV (Comma seperated) file with the server host name and the username to be added
$result = Import-Csv C:\CSV\Computer.csv | ForEach-Object {
$Computer=$_.Computer
$User=$_.user
if (Test-Connection -ComputerName $Computer -Count 1 -Quiet) {
Write-Verbose "$Computer : Online"
$availability="Oniline"
try {
$GroupObj=[ADSI]"WinNT://$Computer/Administrators,group"
$GroupObj.PSBase.Invoke("Add",([ADSI]"WinNT://jdom.edu/$User").Path)
$status="Success"
#Update the status in the output file
[PSCustomObject]#{
Computer = $Computer
Availability = $availability
Status = $status
Date = Get-Date
}
} catch {
Write-Verbose "Failed"
}
} else {
Write-Warning "$Computer : Offline"
$availability = "Offline"
$status = "failed"
#Update the status in the output file
[PSCustomObject]#{
Computer = $Computer
Availability = $availability
Status = $status
Date = Get-Date
}
}
}
$result | Export-Csv -Path $Output -NoTypeInformation
This way, you will store the result into the $result variable and will be able to export it as CSV, without any complication.
Using PowerShell Custom Object is a great way to store data from different sources and provide the output in the way you would like to see it.
Give it a try and provide a feedback, if you would like :)

Creating a script that Moves Computer to an AD OU and Deletes them from SCCM From CSV File

I am basically trying to combine two Powershell scripts that were provided to me. I have most of the script to work together where it imports the computer names from a CSV file. It first moves them to a specific OU in AD and it then deletes them from SCCM.
However, on the last section of the script seems to not work perfectly.The issues starts at
"# Read the csv file for computer records"
For example, I have a computer named "SP3-TEST" but the logs return the error "#{CN=SP3-TEST} not found in SCCM"
For reasons not known to me, it adds the #{CN=} to the computer name.
I have tried to edit the scripts to use only one variable for each need. I am coming from creating batch scripts 90% of my time so maybe I am missing something extremely simple.
#Run script as admin
Set-Location -Path $PSScriptRoot
$fname = $MyInvocation.MyCommand.Name | Out-String
PowerShell -NoProfile -ExecutionPolicy Unrestricted -Command "& {Start-Process PowerShell -ArgumentList '-NoProfile -ExecutionPolicy Unrestricted -File ""$fname""' -Verb RunAs}";
#Importing AD Module
Write-Host " Importing AD Module..... "
import-module ActiveDirectory
Write-Host " Importing Move List..... "
# Reading list of computers from csv and loading into variable
$MoveList = Import-Csv "PC_Move_List.csv"
# defining Target Path
$TargetOU = 'OU=RetiredComputers,OU=XXXXXX,DC=XXXXXX,DC=com'
$countPC = ($movelist).count
Write-Host " Starting import computers ..."
foreach ($Computer in $MoveList){
Write-Host " Moving Computer Accounts..."
Get-ADComputer $Computer.CN | Move-ADObject -TargetPath $TargetOU
Get-ADComputer $Computer.CN | Disable-ADAccount
}
$ScriptDir = Split-Path $script:MyInvocation.MyCommand.Path
$log = "$ScriptDir\clientremoval.log"
$date = Get-Date -Format "dd-MM-yyyy hh:mm:ss"
"--------------------- Script executed on $date (dd-MM-yyyy hh:mm:ss) ---------------------" + "`r`n" | Out-File $log -append
#try import SCCM Module ,if error catch it.
Try
{
Import-Module (Join-Path $(Split-Path $env:SMS_ADMIN_UI_PATH) ConfigurationManager.psd1)
$SiteCode = Get-PSDrive -PSProvider CMSITE
$SMSProvider=$sitecode.SiteServer
Set-Location "$($SiteCode.Name):\"
}
Catch
{
"[ERROR]`t SCCM Module couldn't be loaded. Script will exit!" | Out-File $log -append
Exit 1
}
# Read the csv file for computer records
ForEach ($Computer in $MoveList)
{
$CN=Get-CMDevice -Name $Computer
if ($name)
{
try {
"$date [INFO]`t $name found in SCCM " | Out-File $log -append
Remove-CMDevice -name $Computer -force
"$date [INFO]`t $name removed from SCCM " | Out-File $log -append
}
catch
{"$date [INFO]`t $name found in SCCM but unable to delete record.Check further " | Out-File $log -append
}
}
else
{ "$date [INFO]`t $Computer not found in SCCM " | Out-File $log -append}
}
$MyInvocation.MyCommand.Name
Write-Host " Completed Move List "
Write-Host " $countPC Computers has been moved "
Read-Host -Prompt "Press Enter to exit"
The Powershell script should be trying to delete SP3-TEST not #{CN=SP3-TEST}
Below is the original script.
<#
AUTHOR : Eswar Koneti
DATE : 18-Nov-2016
COMMENT : This script will read notepad file for client computers
,check if they exist and remove from SCCM .
VERSION : 1.0
#>
# Determine script location and create log file to store the results
$ScriptDir = Split-Path $script:MyInvocation.MyCommand.Path
$log = "$ScriptDir\clientremoval.log"
$date = Get-Date -Format "dd-MM-yyyy hh:mm:ss"
"--------------------- Script executed on $date (dd-MM-yyyy hh:mm:ss) ---------------------" + "`r`n" | Out-File $log -append
#try import SCCM Module ,if error catch it.
Try
{
Import-Module (Join-Path $(Split-Path $env:SMS_ADMIN_UI_PATH) ConfigurationManager.psd1)
$SiteCode = Get-PSDrive -PSProvider CMSITE
$SMSProvider=$sitecode.SiteServer
Set-Location "$($SiteCode.Name):\"
}
Catch
{
"[ERROR]`t SCCM Module couldn't be loaded. Script will exit!" | Out-File $log -append
Exit 1
}
# Read the notepad file for client records
ForEach ($client in Get-Content $ScriptDir"\clients.txt")
{
$CN=Get-CMDevice -Name $client
$name=$CN.Name
if ($name)
{
try {
"$date [INFO]`t $name found in SCCM " | Out-File $log -append
Remove-CMDevice -name $client -force
"$date [INFO]`t $name removed from SCCM " | Out-File $log -append
}
catch
{"$date [INFO]`t $name found in SCCM but unable to delete record.Check further " | Out-File $log -append
}
}
else
{ "$date [INFO]`t $client not found in SCCM " | Out-File $log -append}
}

Detect and uninstall antivirus

I have been trying to make a powershell script to detect what antivirus software is installed, and then uninstall it.
I have been able to detect what antivirus is installed using WMI.
I cant find a way to uninstall antivirus software via powershell however.
Is there a way to do this?
Hope you guys can help.
The script i use to detect antivirus:
function Get-AntivirusName {
[cmdletBinding()]
param (
[string]$ComputerName = "$env:computername" ,
$Credential
)
BEGIN
{
$wmiQuery = "SELECT * FROM AntiVirusProduct"
}
PROCESS
{
$AntivirusProduct = Get-WmiObject -Namespace "root\SecurityCenter2" -Query $wmiQuery #psboundparameters
[array]$AntivirusNames = $AntivirusProduct.displayName
Switch($AntivirusNames) {
{$AntivirusNames.Count -eq 0}{"No Antivirus installed";Continue}
{$AntivirusNames.Count -eq 1 -and $_ -eq "Windows Defender"} {"Only Windows Defender is installed!";Continue}
{$_ -ne "Windows Defender"} {"Antivirus installed ($_)."}
}
}
END {
}
}
$av = Get-AntivirusName
Add-Type -AssemblyName PresentationFramework
[System.Windows.MessageBox]::Show($av,'Antivirus')
You could try the following, from https://community.spiceworks.com/scripts/show/3161-detect-and-remove-software-powershell:
################################################
# Powershell Detect and Remove software script #
# #
# V1.0 - Gav #
################################################
# - Edit the Variables below and launch the #
# script as an account that can access the #
# machines. #
# - Script will check that logs exist and #
# create them if needed. #
################################################
cls
#VARIABLES - EDIT BELOW
$software = "INSERT SOFTWARE HERE" # - Enter the name as it appears from WMIC query. WMIC PRODUCT NAME
$textfile = "C:\path\pclist.txt"
$Successlogfile = "C:\path\Done_Machines.txt"
$Errorlogfile = "C:\path\Failed_Machines.txt"
#Date Calculation for Logs
$today = Get-Date
$today = $today.ToString("dddd (dd-MMMM-yyyy)")
#Load PC's From Text File
$computers = Get-Content "$textfile"
#Check if Log Files Exist
If (Test-Path $Successlogfile) {
Write-Host -ForegroundColor Green "Success Log File Exists, Results will be appended"
}
else
{
Write-Host -ForegroundColor Red "Success Log File does not exist, creating log file"
New-Item -path $Successlogfile -ItemType file
}
If (Test-Path $Errorlogfile) {
Write-Host -ForegroundColor Green "Error Log File Exists, Results will be appended"
}
else
{
Write-Host -ForegroundColor Red "Error Log File does not exist, creating log file"
New-Item -path $Successlogfile -ItemType file
}
#Run Ping Test and Uninstall if turned on
foreach ($computer in $computers) {
If (Test-Connection -quiet -ErrorAction SilentlyContinue -computername $computer -count 2)
{
Write-Host -ForegroundColor Green "$Computer is responding, Attempting Uninstall of $Software"
Add-Content $Successlogfile "`n$today`n$Computer`n"
Get-WmiObject -class Win32_Product -ComputerName $computer | Where-Object {$_.Name -match $software} | ForEach-Object { $_.Uninstall()}
}
else
{
Write-Host -ForegroundColor Red "$Computer is not responding"
Add-Content $Errorlogfile "`n$today`n$Computer`n"
}
}