How to catch machine name on copy-item error? - powershell

I'm trying to write a file and delete a file on multiple remote Windows machines. If the machine is not available, ie. not on line I want to capture that in an error log so that I have a list of problematic machine names to send to a help desk. It's probably ugly but I'm close to having something that works. Any clarification would be appreciated.
$from="D:\whatever\machinfo"
$to="\\$machine\c\\scripts\"
output_file="D:\whatever\reports\$machine_writeerror.txt"
foreach($machine in(gc d:\whatever\machinfo\testworkstations.txt))
{
$machine
IF (!$to)
{
Copy-Item D:\whatever\machinfo\010RunGetmachinfo.bat \\$machine \c\scripts -verbose
# $errormsg="destination not found"
$machine > output_file
}
ELSE
{
# DO NOTHING
Remove-Item \\$machine\c\scripts\000dontrun.bat
}
}
OK, I've rewritten this but I'm not doing something right. I want an unique error file that contains either a single file for each machine connection failure or one file that contains the computername of all machines that could not be connected to. I think the following is close (but not right).
$logfile="D:\Projects\StoreControls\machinfo\reports\"+$machine+"_writeerror.txt"
foreach($machine in(gc d:\projects\StoreControls\machinfo\testworkstations.txt))
{
$machine
If ( (Test-Connection -Computername $machine -Quiet -Count 1) -eq "False"){
$machine > $logfile}
Else{
Remove-Item \\$machine\c\scripts\tasks\000dontStart.bat
Copy-Item D:\Projects\StoreControls\machinfo\010RunPCsNServersGetmachinfo.bat \\$machine\c\scripts\tasks\
}
}
Changed "False" to $False after reading more on Test-Connection. Works! Thank you!

you can test the correct execution of a command by testing the automatic variable $?
so you can use something like
Copy-Item D:\whatever\machinfo\010RunGetmachinfo.bat \\$machine\c\scripts
if($? -eq $false){
# copy has failed
"Copy error on $machine" |out-file d:\whatever\reports\$machine_writeerror.txt
}
by the way, a more efficient way could be to ping the host and see if it's alive :
if ( (Test-Connection -ComputerName $machine -Quiet -Count 1) -eq $false){
#host not available
}

Related

Get-Content and foreach in two files

I have two files. The first with contains hostnames (Computers.txt) and the second one contains SID (SID.txt). I want to use Get-Content and foreach to execute a command on each computer with the corresponding SID to modify registry.
Let's take for example PC 1 (first line Computers.txt with first line SID.txt) and PC 2 (second line Computers.txt with second line SID.txt).
$Computer = Get-Content D:\Downloads\computers.txt
$SID = Get-Content D:\Downloads\SID.txt
foreach ($pc in $Computer)
{
Invoke-Command -ComputerName $pc {New-Item HKEY_USERS:\$SID -Name -Vaue}
}
Using a foreach-loop doesn't give you the current linenumber so it's impossible to get the same line from the SIDs-list. You should use a while- or for-loop to create an index that increments by one for each run so you know the "current line".
There's no HKEY_USERS: PSDrive. You need to access it using the Registry-provider, like Registry::HKEY_USERS\
Variables in your local scope (ex. $currentsid) aren't accessible inside the Invoke-Command-scriptblock since it's executed on the remote computer. You can pass it in using -ArgumentList $yourlocalvariable and call it with $args[0] (or put param ($sid) at the beginning of the scriptblock). With PS 3.0+ this is much simpler as you can use the using-scope ($using:currentsid) in your script.
Example:
$Computers = Get-Content D:\Downloads\computers.txt
$SIDs = Get-Content D:\Downloads\SID.txt
#Runs one time for each value in computers and sets a variable $i to the current index (linenumer-1 since arrays start at index 0)
for($i=0; $i -lt $Computers.Length; $i++) {
#Get computer on line i
$currentpc = $Computers[$i]
#Get sid on line i
$currentsid = $SIDs[$i]
#Invoke remote command and pass in currentsid
Invoke-Command -ComputerName $currentpc -ScriptBlock { param($sid) New-Item "REGISTRY::HKEY_USERS\$sid" -Name "SomeKeyName" } -ArgumentList $curentsid
#PS3.0+ with using-scope:
#Invoke-Command -ComputerName $currentpc -ScriptBlock { New-Item "REGISTRY::HKEY_USERS\$using:currentsid" -Name "SomeKeyName" }
}
One-liner:
0..($Computers.Length-1) | ForEach-Object { Invoke-Command -ComputerName $Computers[$_] -ScriptBlock { param($sid) New-Item REGISTRY::HKEY_USERS\$sid -Name "SomeKeyName" } -ArgumentList $SIDs[$_] }
On a side-note: Using two files with matching line numbers is a bad idea. What if comptuers has more lines than SIDs? You should be using a CSV-file that maps computer and SID. Ex..
input.csv:
Computer,SID
PC1,S-1-5-21-123123-123213
PC2,S-1-5-21-123123-123214
PC3,S-1-5-21-123123-123215
This is safer, easier to maintain and you can use it like this:
Import-Csv input.csv | ForEach-Object {
Invoke-Command -ComputerName $_.Computer -ScriptBlock { param($sid) New-Item REGISTRY::HKEY_USERS\$sid -Name "SomeKeyName" } -ArgumentList $_.SID
}

need to output BitLocker status to txt file

I have a simple PowerShell script to check the status of BitLocker drive encryption on a computer on the network. I'd like for the script to determine the status of multiple computers in a text file.
Here's my basic script so far that Nathan Rice had helped with:
$TextFilePath = Read-Host "What is the path to the text file?"
If (Test-Path $TextFilePath) {
$ComputersArray = Get-Content $TextFilePath
ForEach ($Computer in $ComputersArray) {
If (Test-Connection $Computer -Count 1) {
$ComputerStatus = manage-bde -status -cn "$Computer"
Write-Host($ComputerStatus)
} Else {
Write-Host("$Computer appears to be offline.")
}
}
} Else {
Write-Error "The text file was not found, check the path."
}
I modified the code but it only writes one result to the text file, meaning if I have 5 computers in the list, it writes only the results for the first computer:
$TextFilePath = Read-Host "What is the path to the text file?"
If (Test-Path $TextFilePath){
$ComputersArray = Get-Content $TextFilePath
ForEach ($Computer in $ComputersArray) {
If (Test-Connection $Computer -Count 1) {
$ComputerStatus = manage-bde -status -cn "$Computer" |
Out-File -filepath "c:\users\enduser\Bitlocker-Status.txt"
} Else {
Write-Host("$Computer appears to be offline.")
}
}
} Else {
Write-Error "The text file was not found, check the path."
}
I'd like it to write the results for each device to the list.
Create a collection of results. After the loop, write the collection to a file.
$TextFilePath = Read-Host "What is the path to the text file?"
If (Test-Path $TextFilePath){
$ComputersArray = Get-Content $TextFilePath
$ComputerStatusCol = #()
ForEach ($Computer in $ComputersArray) {
If (Test-Connection $Computer -Count 1){
$ComputerStatus = manage-bde -status -cn "$Computer"
$ComputerStatusCol += $ComputerStatus
} Else {
Write-Host("$Computer appears to be offline.")
}
}
$ComputerStatusCol | Out-File -filepath "c:\users\enduser\Bitlocker-Status.txt"
} Else {
Write-Error "The text file was not found, check the path."
}
You need to add the parameter -Append to the Out-File, so that your output is appended to the existing content instead of replacing it:
$ComputerStatus = manage-bde -status -cn "$Computer" |
Out-File -filepath "c:\users\enduser\Bitlocker-Status.txt" -append -force
Easy batch file for admins who want a nice easy file to look through. Just set this up at one of my clients AD Networks, worked like a charm:
Setup a .cdm file, dump it into the netlogon folder
script:
echo Computer:%ComputerName% with username:%username% - Bitlocker check of drive C: >> "\server\share\folder\BitlockerCheck.log"manage-bde -status c: >> "\server\share\folder\BitlockerCheck\BitlockerCheck.log"
Make sure everyone has access to share path (domain users)Edit Group Policy for the container you want it to run in (default domain policy should never be touched, if you want everyone, make a new policy at the top and name it Bitcloker status check).
Go to User Configuration - Policies - Windows Settings - Scripts Right-click Logon, properties, Add - browse to \dcname\netlogon\filename.cmdclick OK, after about 15 minutes (without a forced gpupdate) the file will start populating as users logon/logoff.
On Non-BitLocker computers, it will show the computer name and user with no info.May be cumbersome on very large networks, but you could break out Gp script by OU and separate files as most large companies don't have everyone in one container.

Powershell Script to Update DNS on multiple servers

I have received the following script to update the DNS server on a windows server. This script works great for updating one server. I would like to have this script process a text file with a list of servers to batch update multiple servers at once. I am thinking something like:
script.ps1 -ComputerName (Get-Content c:\serverlist.txt) -OldDns 10.0.0.1 -NewDns 10.0.0.2
This fails if there are multiple servers in the 'serverlist.txt'. My question is how to incorporate a ForEach that allows for every server in the 'serverlist.txt'
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True,HelpMessage="Name of the computer to update")]
[String]$ComputerName,
[Parameter(Mandatory=$True,HelpMessage="DNS server to replace")]
[String]$OldDns,
[Parameter(Mandatory=$True,HelpMessage="New DNS server setting")]
[String]$NewDns,
[switch]$TestMode
)
$niclist = Get-WmiObject -Authentication PacketIntegrity -ComputerName $ComputerName -class Win32_NetworkAdapterConfiguration | where { $_.DnsServerSearchOrder -contains $OldDns }
if ($niclist) {
foreach ($nic in $niclist) {
$dns = $nic.DnsServerSearchOrder
$dns
$index = [array]::IndexOf($dns,$OldDns)
$dns[$index] = $NewDns
$dns
$nic
if (!$TestMode) {
$nic.SetDnsServerSearchOrder($dns)
}
}
}
Assuming your serverlist.txt contains a list of servers where one is on each line and no headers you can just pipe the content of the file into a ForEach-Object loop. The -ComputerName will be $_ which is the current item in the pipeline.
Get-Content c:\serverlist.txt | ForEach-Object{
script.ps1 -ComputerName $_ -OldDns 10.0.0.1 -NewDns 10.0.0.2
}

Powershell get-process query

I want to write a simple If Statement which checks if an Process Exists.
If it Exists, something should start.
Like this, but working.. ;)
If ((Get-Process -Name Tvnserver.exe ) -eq $True)
{
Stop-Process tnvserver
Stop-Service tvnserver
Uninstall...
Install another Piece of Software
}
Else
{
do nothing
}
Thanks
Get-Process doesn't return a boolean value and the process name is listed without extension, that's why your code doesn't work. Drop the extension and either check if the result is $null as Musaab Al-Okaidi suggested, or cast the result to a boolean value:
if ( [bool](Get-Process Tvnserver -EA SilentlyContinue) ) {
# do some
} else {
# do other
}
If you don't want the script to do anything in case the process isn't running: just omit the else branch.
This will evaluate to true if the process doesn't exist:
(Get-Process -name Tvnserver.exe -ErrorAction SilentlyContinue) -eq $null
or if you want to change it you can negate the statement as follows:
-not ( $(Get-Process -name Tvnserver.exe -ErrorAction SilentlyContinue) -eq $null )
It's important to have have -ErrorAction SilentlyContinue to avoid any errors been thrown if a process doesn't exist.

file location different on computers and space issues

I have the below code and currently checks computer for version info and service status.
The problem I have is that servers have the located .exe in different places:
C:\program files\snare\snarecore.exe
C:\program files (x86)\snare\snarecore.exe
D:\apps\snare\snarecore.exe
How do I get the script below to run the right version? I think I can use the path that the service is checking? I am doing this all remotly and have rights to the server and works fine - but I am having to possiblt make three (or more!) scripts just based on all three locations of where the executable is!
Also, for the ones that have a space in the name (../program file..) where do I put the quotes so that powershell can read the whole line and not error out due to the space in the name?
CODE:
clear
$ErrorActionPreference = "silentlycontinue"
$Logfile = "C:\temp\output_cdrive.log"
Function LogWrite
{
param([string]$logstring)
Add-Content $Logfile -Value $logstring
}
$computer = Get-Content -Path c:\temp\servers2.txt
foreach ($computer1 in $computer){
$Service = Get-WmiObject Win32_Service -Filter "Name = 'Snare'" -ComputerName $computer1
if (test-connection $computer1 -quiet)
{
$version = (Get-Command ""\\$computer1\c$\Program Files (x86)\Snare\SnareCore.exe"").FileVersionInfo.FileVersion
if($Service.state -eq 'Running')
{
LogWrite "$computer1 STARTED $version"
}
else
{
LogWrite "$computer1 STOPPED $version"
}
}
else
{
LogWrite "$computer1 is down" -foregroundcolor RED
}
}
Thanks,
you can check the pathname property of you service to get the exe location :
PS>(Get-WmiObject Win32_Service -Filter "name='spooler'").pathname
C:\Windows\System32\spoolsv.exe
The obvious solution is to use a path variable, assign that variable each of the three different paths in turn, and write your actual checks as a function using that variable.
You can escape the double quotes using the backtick:
"`"\\$computer1\c$\Program Files (x86)\Snare\SnareCore.exe`""