PowerShell variables from csv - powershell

I have a problem with using the variables in my script coming from a csv file. Everytinme the text UNC is found, I want to do something. And when the text UNC is not found, I want to do something else. This is great for running a script locally or on the remote server.
Script
$File = (Import-Csv -Path S:\Input\Auto_Clean.csv | Select-String -NotMatch "#")
Foreach ($Item in $File) {
If ( $SERVER -eq "UNC" ) {
Write-Host "UNC: $SERVER, $PATH, $OlderThanDays" }
else {
Write-Host "Not UNC: $SERVER, $PATH, $OlderThanDays"}
}
CSV-file
# Input file:
SERVER, PATH, OlderThanDays
Server1, E:\Share\Dir1, 10
Server2, F:\Share\Dir2, 5
UNC, \\Domain\Share\Dir1, 30
Desired result in cosole
Not UNC: Server1, E:\Share\Dir1, 10
Not UNC: Server2, F:\Share\Dir2, 5
UNC: UNC, \\Domain\Share\Dir1, 30
The output of Write-Host $Fileis correctly displaying the labels/variants:
Write-Host $File
#{SERVER=UNC; PATH=\\domain\Share\Dir1; OlderThanDays=10}
Thank you for your help.

I think this is what you want:
Import-Csv -Path "S:\Input\Auto_Clean.csv" | % {
if ($_.Server -eq "UNC")
{
Write-Host "UNC: $($_.Server), $($_.Path), $($_.OlderThanDays)"
}
else
{
Write-Host "Not UNC: $($_.Server), $($_.Path), $($_.OlderThanDays)"
}
}
This produces the following output:
Not UNC: Server1, E:\Share\Dir1, 10
Not UNC: Server2, F:\Share\Dir2, 5
UNC: UNC, \\Domain\Share\Dir1, 30
If you want to ignore lines starting with # you can use the following:
(Get-Content "S:\Input\Auto_Clean.csv") -notmatch '^#' | ConvertFrom-CSV | % {
if ($_.Server -eq "UNC")
{
Write-Host "UNC: $($_.Server), $($_.Path), $($_.OlderThanDays)"
}
else
{
Write-Host "Not UNC: $($_.Server), $($_.Path), $($_.OlderThanDays)"
}
}

Related

Powershell - Make a menu out of text file

In my adventure trying to learn Powershell, I am working on an extension on a script I have made. The idea is to make script there by adding ".iso" files into a folder. It will use that content in a menu so that I later can use it to select an iso file for a WM in Hyper-V
This is my version of how it will get the content in the first place
Get-ChildItem -Path C:\iso/*.iso -Name > C:\iso/nummer-temp.txt
Add-Content -Path C:\iso/nummer.txt ""
Get-Content -Path C:\iso/nummer-temp.txt | Add-Content -Path C:\iso/nummer.txt
When this code is run it will send an output like what i want. But my question is how do I use this output in a menu?
This is the best practice way to do so in powershell :
#lets say your .txt files gets this list after running get-content
$my_isos = $('win7.iso','win8.iso','win10.iso')
$user_choice = $my_isos | Out-GridView -Title 'Select the ISO File you want' -PassThru
#waiting till you choose the item you want from the grid view
Write-Host "$user_choice is going to be the VM"
I wouldn't try to make it with System.windows.forms utilities as i mentioned in my comment, unless you want to present the form more "good looking".
If you don't want to go for a graphical menu, but rather a console menu, you could use this function below:
function Show-Menu {
Param(
[Parameter(Position=0, Mandatory=$True)]
[string[]]$MenuItems,
[string] $Title
)
$header = $null
if (![string]::IsNullOrWhiteSpace($Title)) {
$len = [math]::Max(($MenuItems | Measure-Object -Maximum -Property Length).Maximum, $Title.Length)
$header = '{0}{1}{2}' -f $Title, [Environment]::NewLine, ('-' * $len)
}
# possible choices: digits 1 to 9, characters A to Z
$choices = (49..57) + (65..90) | ForEach-Object { [char]$_ }
$i = 0
$items = ($MenuItems | ForEach-Object { '{0} {1}' -f $choices[$i++], $_ }) -join [Environment]::NewLine
# display the menu and return the chosen option
while ($true) {
cls
if ($header) { Write-Host $header -ForegroundColor Yellow }
Write-Host $items
Write-Host
$answer = (Read-Host -Prompt 'Please make your choice').ToUpper()
$index = $choices.IndexOf($answer[0])
if ($index -ge 0 -and $index -lt $MenuItems.Count) {
return $MenuItems[$index]
}
else {
Write-Warning "Invalid choice.. Please try again."
Start-Sleep -Seconds 2
}
}
}
Having that in place, you call it like:
# get a list if iso files (file names for the menu and full path names for later handling)
$isoFiles = Get-ChildItem -Path 'D:\IsoFiles' -Filter '*.iso' -File | Select-Object Name, FullName
$selected = Show-Menu -MenuItems $isoFiles.Name -Title 'Please select the ISO file to use'
# get the full path name for the chosen file from the $isoFiles array
$isoToUse = ($isoFiles | Where-Object { $_.Name -eq $selected }).FullName
Write-Host "`r`nYou have selected file '$isoToUse'"
Example:
Please select the ISO file to use
---------------------------------
1 Win10.iso
2 Win7.iso
3 Win8.iso
Please make your choice: 3
You have selected file 'D:\IsoFiles\Win8.iso'

Powershell script suddenly failing

We have a script running daily on two separate servers, there have been no changes made to either copy of the script all year. Last weekend, there was a server outage that interrupted the script running on one of the servers, and ever since the script on that server partially fails each day.
Here is the code which continues to fail, I have broken it out and ran it locally without issue.
$rawlineCountFile ="C:\temp\files\test\linecount"
$rawlineCountFile = $rawlineCountFile +'RawlineCount' + 'test' + '.csv'
$filePath = "C:\temp\files\test"
# do line count in files
$bak = Get-ChildItem $filePath | Where-Object { $_.Extension -eq ".dat" }
Try
{
Write-Output "line_count , file_name"
foreach ($file in $bak) {
$name = $file.name
$measure =(Get-Content $filePath\$file | Measure-Object)
$lines = $measure.Count
Write-Output "$lines , $name"
Write-Output "$lines , $name" >> $rawlineCountFile
}
} catch [Exception] {
Write-Output $_.Exception.GetType().FullName
Write-Output $_.Exception.Message
}
This script above looks at a folder with .dat files and FOR EACH one, it writes the $lines within each file ($measure.count) and the $file.name into a rawlinecountfile.csv.
i.e.
123 , file1.dat
234 , file2.dat
987 , file3.dat
567 , file4.dat
etc. etc.
Each day there are 7 files moved into this folder, then this script runs, so there should be 7 rows added to the rawlinecountfile each day also, then later, after the rest of the process finishes, all the files are cleared out to prepare for the next day.
However, since the outage last week, it only writes 0-2 out of 7 rows onto the csv file each day, not FOR EACH file (still 7).
We are stumped as to why the For Each doesn't seem to be working anymore, while the script has not been changed and the same exact script still runs as expected on the sister server, and on my local machine.
Any thoughts?
When you were calling Get-Content, I believe you are trying to get the file from C:\Temp\Files\Test\C:\Temp\Files\Test\Filename.dat. You can use $file.FullName to get the full path. I suspect it was probably throwing an error and not adding the content to the file. This worked in PS 5.1.
$rawlineCountFile ="C:\temp\files\test\linecount"
$rawlineCountFile = $rawlineCountFile +'RawlineCount' + 'test' + '.csv'
$filePath = "C:\temp\files\test"
# do line count in files
$bak = Get-ChildItem $filePath | Where-Object { $_.Extension -eq ".dat" }
Try
{
Write-Output "line_count , file_name"
foreach ($file in $bak)
{
$name = $file.name
$lines = #(Get-Content -Path $File.FullName).Count
Write-Output "$lines , $name"
"$lines , $name" | Add-Content $rawlineCountFile
}
}
catch [Exception]
{
Write-Output $_.Exception.GetType().FullName
Write-Output $_.Exception.Message
}
Replacing just the single line I mentioned that could be an issue:
$rawlineCountFile ="C:\temp\files\test\linecount"
$rawlineCountFile = $rawlineCountFile +'RawlineCount' + 'test' + '.csv'
$filePath = "C:\temp\files\test"
# do line count in files
$bak = Get-ChildItem $filePath | Where-Object { $_.Extension -eq ".dat" }
Try
{
Write-Output "line_count , file_name"
foreach ($file in $bak) {
$name = $file.name
$measure =(Get-Content $file.FullName | Measure-Object)
$lines = $measure.Count
Write-Output "$lines , $name"
Write-Output "$lines , $name" >> $rawlineCountFile
}
} catch [Exception] {
Write-Output $_.Exception.GetType().FullName
Write-Output $_.Exception.Message
}

Powershell error with else statement

I am trying to write a script that will remove old queues from users HKLM (will eventually delete from HKCU by mounting ntuser.dat but I am not there yet).
The problem I am having is that I am only iterating through one sid under SOFTWARE\Microsoft\Windows NT\CurrentVersion\Print\Providers\Client Side Rendering Print Provider\ and I get the following error message:
The term 'else' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is corr
ect and try again.
Has anyone ran into this issue before?
#defining my object that will be used throughout the script. Will be used to log everything
$objQueueData=[pscustomobject]#{
computername=""
computerstatus=""
Registrystatus=""
SID=""
Does_It_Have_2003_Queues=""
User_SID_Status=""
user=""
UNC_2003_Queues=""
}
#$QueueDataCollection=[pscustomobject]#{
#queuecollection=$QueueData
#}
#reading the list of workstations
Get-Content "P:\PowerShell\jm\DeletePrintQueues\Workstations.txt" | ForEach-Object{
$strComputerName = $_
#check if the workstation is up
IF (Test-Connection -ComputerName $strComputerName -count 2 -quiet)
{
#$objUser= Get-ChildItem c:\users
#$strUserName=$objUser.Name
$objQueueData.computername=$strComputerName
$objQueueData.computerstatus="Machine is up"
DeleteHklm $strComputerName
}
else
{
#We are here because the computer could not be reached
Write-Host "Machine down" $strComputerName
$objQueueData.computername =$strComputerName
$objQueueData.computerstatus = "Machine Down"
$objQueueData.Registrystatus ="Machine Down"
$objQueueData.SID = "Machine Down"
$objQueueData.Does_It_Have_2003_Queues="Machine Down"
$objQueueData.User_SID_Status="Machine Down"
$objQueueData.user="Machine Down"
$objQueueData.UNC_2003_Queues="Machine Down"
$objQueueData | Export-Csv P:\powershell\jm\results2.csv -NoTypeInformation -Append
}
}
function DeleteHKLM {
param ([string]$computername)
try{
If($strHklm = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine',$strcomputername ))
{
#executes when it can open HKLM
$objqueuedata.RegistryStatus = "Was able to open the registry"
#set the path of the registry
$PrinterRegKey = 'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Print\\Providers\\Client Side Rendering Print Provider'
#$PrinterRegKey
$regPrinterRef = $strHklm.OpenSubKey($PrinterRegKey)
#debug
Write-Host "regprinterref is: "$regPrinterRef
}
If($regPrinterRef)
{
#This executes if there are Printers present in the registry
#region Loop thru all child keys. These contain the calculable UNC paths to 2003
$regPrinterRef.GetSubKeyNames() | ForEach-Object{
#debug
Write-Host "The sid is: " $_
#concatinating to get to the connections key
#$PrinterRegKey
$strPrinterpath =$PrinterRegKey+"\\"+ $_ + "\\Printers\\Connections"
#debug
Write-Host "The printer keys for SID are located in: "
$strPrinterPath
if ($strPrinterpath -notlike "*servers*")
{
#this value is the sid
# $_ will give us the sids. Here I am storing the SIDs into strUserSID to use later on
$strUserSID = $_
#debug
# $strUserSID
# The logic below will convert SID to username
#pass the SID to the secrity principal SID being struserSID
$objSID = New-Object System.Security.Principal.SecurityIdentifier("$strUserSID")
#using a try catch to filter out deleted SIDs, otherwise powershell will throw an error saying it is null
Try{
$strUser = $objSID.Translate( [System.Security.Principal.NTAccount]).Value
$objQueueData.User_SID_Status ="Valid SID"
$strUser
}
Catch{
#$strUserID = $objSID.Value
$objQueueData.User_SID_Status ="Invalid SID"
$objQueueData.User = "Invalid SID"
$objQueueData.Does_it_Have_2003_Queues ="Invalid SID"
$objQueueData.UNC_2003_Queues = "Invalid SID"
$objQueueData | Export-Csv P:\powershell\jm\results1.csv -NoTypeInformation -Append
#exit
}
$regPrinterDetails = $Strhklm.OpenSubKey($strPrinterPath)
$regPrinterDetails.GetSubKeyNames() |ForEach-Object{
#looping through each key at the connections level to search for the 2003 print server names
if($_ -like "*sarspprt2*")
{
$objQueueData.Does_It_Have_2003_Queues = "Yes"
#this value is the printer if it exists
# $_ will give us the printers. Here I am storing the printers into strUserPrinters to user later on
$strUserPrinters = $_
Write-Host "struserprinters value is " $_
#$strUserPrinters
$blnHasOldQueues = $true
#The code below is to build the printer UNC to make it more legible
$intPrinterLength= $strUserPrinters.Length
$strPrintServer= $strUserPrinters.Substring(2,10)
#Doing the -13 because we have to limit the length of the substring statement to the length minus the starting poistion of the substring
$strPrinterShareName =$strUserPrinters.Substring(13,$intPrinterLength-13)
$strPrintUNC = "\\"+$strPrintServer+"\"+$strPrinterShareName
$objQueueData.UNC_2003_Queues = $strPrintUNC
$objQueueData.User = $strUser
$objQueueData | Export-Csv P:\powershell\jm\results.csv -NoTypeInformation -Append
$strkeytodelete=$strPrinterPath+"\\"+$_
$strkeytodelete
#delete 2003 Key
Remove-Item -Path '$strkeytodelete' -Recurse
}
elseif($_ -notlike "*sarspprt2*")
{
#Write-host "No 2003 Queues Detected"
#Write-host "no 2003 Queues detected" $strUserSID,$strUser,$strPrintUNC
$objQueueData.User = $strUser
$objQueueData.Does_it_Have_2003_Queues = "No 2003 Queues Detected for this user or vsarspprt* queue"
$objQueueData.UNC_2003_Queues = "No 2003 Queues Detected for this user or vsarspprt* queue"
$objQueueData | Export-Csv P:\powershell\jm\results.csv -NoTypeInformation -Append
}
# Write-Host $strServer $blnHasOldQueues $strUserSID $strUserPrinters
}
}
}
else
{
#Write-Host "No Printers in the Registry"
$objQueueData.computername=""
$objQueueData.computerstatus=""
$objQueueData.Registrystatus=""
$objQueueData.SID=""
$objQueueData.Does_It_Have_2003_Queues=""
$objQueueData.User_SID_Status=""
$objQueueData.user=""
$objQueueData.UNC_2003_Queues=""
}
}
}
catch{
# Write-Host "cant read registry"
$_.Exception.Message
}
}
You have an extra curly brace on line 153. If you move that to after line 165 it should work, although I can't test it right now. I got in the habit of systematically collapsing my if-else statements to ensure that they all match up with eachother.

Powershell output formatting?

I have a script that scans for a specific folder in users AppData folder. If it finds the folder, it then returns the path to a txt file. So we can see the computer name and username where it was found.
I would like to be able to format the what is actually written to the text file, so it removes everything from the path except the Computer and User names.
Script:
foreach($computer in $computers){
$BetterNet = "\\$computer\c$\users\*\AppData\Local\Google\Chrome\User Data\Default\Extensions\gjknjjomckknofjidppipffbpoekiipm"
Get-ChildItem $BetterNet | ForEach-Object {
$count++
$betternetCount++
write-host BetterNet found on: $computer
Add-Content "\\SERVERNAME\PowershellScans\$date\$time\BetterNet.txt" $_`n
write-host
}
}
The text files contain information like this
\\computer-11-1004S10\c$\users\turtle\AppData\Local\Google\Chrome\User Data\Default\Extensions\gjknjjomckknofjidppipffbpoekiipm
\\computer-1004-24S\c$\users\camel\AppData\Local\Google\Chrome\User Data\Default\Extensions\gjknjjomckknofjidppipffbpoekiipm
\\computer-1004-23S\c$\users\rabbit\AppData\Local\Google\Chrome\User Data\Default\Extensions\gjknjjomckknofjidppipffbpoekiipm
If you have each line in a form of the string $string_containing_path then it is easy to split using split method and then add index(1) and (4) that you need:
$afterSplit = $string_containing_path.Split('\')
$stringThatYouNeed = $afterSplit[1] + " " + $afterSplit[4]
You can also use simple script that will fix your current logs:
$path_in = "C:\temp\list.txt"
$path_out= "C:\temp\output.txt"
$reader = [System.IO.File]::OpenText($path_in)
try {
while($true){
$line = $reader.ReadLine()
if ($line -eq $null) { break }
$line_after_split_method = $line.Split('\')
$stringToOutput = $line_after_split_method[1] + " " + $line_after_split_method[4] + "`r`n"
add-content $path_out $stringToOutput
}
add-content $path_out "End"
}
finally {
$reader.Close()
}
If you split your loop into two foreach loops, one for computer and user directory it would be easier to output the name of the user directory.
$output = foreach($computer in $computers){
$UserDirectories = Get-ChildItem "\\$computer\c$\users\" -Directory
foreach ($Directory in $UserDirectories) {
$BetterNet = Get-ChildItem (Join-Path $Directory.fullname "\AppData\Local\Google\Chrome\User Data\Default\Extensions\gjknjjomckknofjidppipffbpoekiipm")
Add-Content "\\SERVERNAME\PowershellScans\$date\$time\BetterNet.txt" "$computer $($Directory.name)`r`n"
write-host BetterNet found on: $computer
$BetterNet
}
}
$output.count

Powershell Host File edit

Guys i'm having some issues converting my Perl script to powershell, I need some help. In the host file of our machines, we have all of the URL's to our test environments blocked. In my PERL script, based on which environment is selected, it will comment out the line of the environment selected to allow access and block others so the testers can't mistakenly do things in the wrong environment.
I need help converting to powershell
Below is what I have in PERL:
sub editHosts {
print "Editing hosts file...\n";
my $file = 'C:\\Windows\\System32\\Drivers\\etc\\hosts';
my $data = readFile($file);
my #lines = split /\n/, $data;
my $row = '1';
open (FILE, ">$file") or die "Cannot open $file\n";
foreach my $line (#lines) {
if ($line =~ m/$web/) {
print FILE '#'."$line\n"; }
else {
if ($row > '21') {
$line =~ s/^\#*127\.0\.0\.1/127\.0\.0\.1/;
$line =~ s/[#;].*$//s; }
print FILE "$line\n"; }
$row++;
}
close(FILE);
}
Here is what i've tried in Powershell:
foreach ($line in get-content "C:\windows\system32\drivers\etc\hosts") {
if ($line -contains $web) {
$line + "#"
}
I've tried variation including set-content with what used to be in the host file, etc.
Any help would be appreciated!
Thanks,
Grant
-contains is a "set" operator, not a substring operator. Try .Contains() or -like.
This will comment out lines matching the variable $word, while removing # from non-matches (except the header):
function Edit-Hosts ([string]$Web, $File = "C:\windows\system32\drivers\etc\hosts") {
#If file exists and $web is not empty/whitespace
if((Test-Path -Path $file -PathType Leaf) -and $web.Trim()) {
$row = 1
(Get-Content -Path $file) | ForEach-Object {
if($_ -like "*$web*") {
#Matched PROD, comment out line
"#$($_)"
} else {
#No match. If past header = remove comment
if($row -gt 21) { $_ -replace '^#' } else { $_ }
}
$row++
} | Set-Content -Path $file
} else {
Write-Error -Category InvalidArgument -Message "'$file' doesn't exist or Web-parameter is empty"
}
}
Usage:
Edit-Hosts -Web "PROD"
This is a similar answer to Frode F.'s answer, but I'm not yet able to comment to add my 2c worth, so have to provide an alternative answer instead.
It looks like one of the gotchas moving from perl to PowerShell, in this example, is that when we get the content of the file using Get-Content it is an "offline" copy, i.e. any edits are not made directly to the file itself. One approach is to compile the new content to the whole file and then write that back to disk.
I suppose that the print FILE "some text\n"; construct in perl might be similar to "some text" | Out-File $filename -Encoding ascii -Append in PowerShell, albeit you would use the latter either (1) to write line-by-line to a new/empty file or (2) accept that you are appending to existing content.
Two other things about editing the hosts file:
Be sure to make sure that your hosts file is ASCII encoded; I have caused a major outage for a key enterprise application (50k+ users) in learning that...
You may need to remember to run your PowerShell / PowerShell ISE by right-clicking and choosing Run as Administrator else you might not be able to modify the file.
Anyway, here's a version of the previous answer using Out-File:
$FileName = "C:\windows\system32\drivers\etc\hosts"
$web = "PROD"
# Get "offline" copy of file contents
$FileContent = Get-Content $FileName
# The following creates an empty file and returns a file
# object (type [System.IO.FileInfo])
$EmptyFile = New-Item -Path $FileName -ItemType File -Force
foreach($Line in $FileContent) {
if($Line -match "$web") {
"# $Line" | Out-File $EmptyFile -Append -Encoding ascii
} else {
"$Line" | Out-File $EmptyFile -Append -Encoding ascii
}
}
Edit
The ($Line -match "$web") takes whatever is in the $web variable and treats it as a regular expression. In my example I was assuming that you were just wanting to match a simple text string, but you might well be trying to match an IP address, etc. You have a couple of options:
Use ($Line -like "*$web*") instead.
Convert what is in $web to be an escaped regex, i.e. one that will match literally. Do this with ($Line -match [Regex]::Escape($web)).
You also wanted to strip off comments from any line past row 21 of the hosts file, should that line not match $web. In perl you have used the s substitution operator; the PowerShell equivalent is -replace.
So... here is an updated version of that foreach loop:
$LineCount = 1
foreach($Line in $FileContent) {
if($Line -match [Regex]::Escape($web) {
# ADD comment to any matched line
$Line = "#" + $Line
} elseif($LineCount -gt 21) {
# Uncomment the other lines
$Line = $Line -replace '^[# ]+',''
}
# Remove 'stacked up' comment characters, if any
$Line = $Line -replace '[#]+','#'
$Line | Out-File $EmptyFile -Append -Encoding ascii
$LineCount++
}
More Information
Are there good references for moving from Perl to Powershell?
How to use operator '-replace' in PowerShell to replace strings of texts with special characters and replace successfully
about_Comparison_Operators
http://www.comp.leeds.ac.uk/Perl/sandtr.html
If you wanted to verify what was in there and then add entries, you could use the below which is designed to be ran interactively and returns any existing entries you specify in the varibles:
Note: the `t is powershell's in script method for 'Tab' command.
$hostscontent
# Script to Verify and Add Host File Entries
$hostfile = gc 'C:\Windows\System32\drivers\etc\hosts'
$hostscontent1 = $hostfile | select-string "autodiscover.XXX.co.uk"
$hostscontent2 = $hostfile | select-string "webmail.XXX.co.uk"
$1 = "XX.XX.XXX.XX`tautodiscover.XXX.co.uk"
$2 = "webmail.XXX.co.uk"
# Replace this machines path with a path to your list of machines e.g. $machines = gc \\machine\machines.txt
$machines = gc 'c:\mytestmachine.txt'
ForEach ($machine in $machines) {
If ($hostscontent1 -ne $null) {
Start-Sleep -Seconds 1
Write-Host "$machine Already has Entry $1" -ForegroundColor Green
} Else {
Write-Host "Adding Entry $1 for $machine" -ForegroundColor Green
Start-Sleep -Seconds 1
Add-Content -Path C:\Windows\System32\drivers\etc\hosts -Value "XX.XX.XXX.XX`tautodiscover.XXX.co.uk" -Force
}
If ($hostscontent2 -ne $null) {
Start-Sleep -Seconds 1
Write-Host "$machine Already has Entry $2" -ForegroundColor Green
} Else {
Write-Host "Adding Entry $2 for $machine" -ForegroundColor Green
Start-Sleep -Seconds 1
Add-Content -Path C:\Windows\System32\drivers\etc\hosts -Value "XX.XX.XXX.XX`twebmail.XXX.co.uk" -Force
}
}