Sunday, January 6, 2019

Powershell script to perform OCR on images present in a folder

This powershell script can perform OCR via tesseract OCR and convert them to text files default supported language is Kannada by script, but you can change it to required language.

Script asks you to choose your source folder and also the output folder to save converted files to.

Requirements to run this script: Tesseract OCR for windows software.
Running powershell script should be enabled by running set-executionpolicy unrestricted in powershell.

Save the below script with convert-ImagestoText.ps1 or any name with .ps1 extension and run it after setting the execution policy.

To run the script, you can right click on it and select run with powershell or run it from the powershell console by typing .\filename.ps1

Please contact me if you need any help related to this 

Important note: Please make sure that there is no space in any foldername while providing input and output folders, or else script might fail(I faced this issue today 7/1/2018).

param($SourceFolderPath,$OutputFolderPath,$LanguageCode = "kan")
Function Get-Folder($rootFolder,$DialogBoxTitleMessage)
[System.Reflection.Assembly]::LoadWithPartialName("") | Out-Null

$foldername = New-Object System.Windows.Forms.FolderBrowserDialog
$foldername.Description = $DialogBoxTitleMessage
$foldername.SelectedPath = $rootFolder

if($foldername.ShowDialog() -eq "OK")
$folder += $foldername.SelectedPath
return $folder
if(!($SourceFolderPath -and $OutputFolderPath))
$rootFolderForSelector = "$env:userprofile\desktop"
$SourceFolderPath = Get-Folder -rootFolder $rootFolderForSelector -DialogBoxTitleMessage "Please select Source folder with images"
$OutputFolderPath = Get-Folder -rootFolder $rootFolderForSelector -DialogBoxTitleMessage "Please select folder to save the converted files"
if($SourceFolderPath -and $OutputFolderPath)
$filterFiles = "*.jp*g","*.png","*.bmp"

foreach($filterString in $filterFiles)
Write-Information -MessageData "Getting files of type $filterString" -InformationAction Continue
$inputFiles += Get-ChildItem $SourceFolderPath -Filter $filterstring
$totalFiles = $inputfiles.Count
$count = 0;
$inputFiles | ForEach-Object{
$inputFileFullName = $_.FullName
$outputfileName = Join-Path $OutputFolderPath "$($_.BaseName)"
try {
#Write-Information -MessageData "Converting file $inputFileFullName" -InformationAction Continue
$perc = (100*$count)/$totalfiles
Write-Progress -Activity "OCR conversion" -PercentComplete $perc -Status "$perc %" -currentoperation "Converting $inputfilefullname"
start-process tesseract.exe -argumentlist $inputFileFullName,$outputfileName,"-l",$LanguageCode -nonewwindow -wait

catch {
Write-Warning "Error while converting $inputFileFullName"
Write-Warning $_
Running via commandline with source folder and destination folder as input

Running without inputs, choosing folders via windows prompt

Tesseract OCR project page for more options and information:

Thursday, January 3, 2019

How to convert djvu to text

I had some djvu documents which had actual text in them, I was able to open them in djvu viewer and select the text and paste them into text files. I wanted to find an easy way to convert all the files into text documents, I had more than 50 files to convert each of them having pages over 500.

I found a commandline tool which can convert/extract the hidden text from djvu files and wrote a powershell script to pass the directories and convert the files.

Please note that if the djvu file does not contain any text this method might not be useful. To confirm that your djvu file has extractable text do the following.

Open djvu file in djvu viewer, click on edit->Select, select the area containing text and copy and paste it into a notepad file, if you see the text pasted, this tutorial should be helpful.

Software Requirements
1. djvulibre package
2. Windows powershell

You need to use the djvutxt.exe tool from djvulibre package in order to extract text from djvu documents.

You can download and install it from following link: select windows download link.
Once the setup file is downloaded double click and install it.

Once you complete the installation add the installation directory to environment variable path.
 refer this link for steps

Open windows powershell by searching it in windows search.

Run the following command to check the execution policy(powershell execution policy should be set to allow running scripts on your computer)
if you get the result as unrestricted then you can continue.
If your policy is restricted, you can execute the following command to enable it
Set-ExecutionPolicy -ExecutionPolicy Unrestricted
(Once you complete the task you can set it to the one whichever was the output of get-executionpolicy command above)

Save the below code in required directory with .ps1 extension, that is when you save the file name it something like DJVU-BulkConvertor.ps1

ps1 is the powershell script extension, in simple terms if you are unfamiliar with powershell, powershell is a new technology implemented by Microsoft which supports scripting and commandline shell similar to DOS but in the back-end it is built with lot of features and strong design which has changed the way system admins used to work on windows. 

 You need to change the directory paths to your input directory path in below script, first two lines.

The folder structure is like below
FolderName3 -> contains multiple folders, inside each folder I have only djvu files no further directories.
Output will be written to  C:\FolderName1\FolderName2 creating folders which were inside foldername3
that is
C:\FolderName1\FolderName2\MyOutput and so on

$source = "C:\FolderName1\FolderName2\FolderName3"
$OutputFolderName = "MyOutput"
$outputDir = Split-Path -Parent $source | ForEach-Object{Join-Path $_ $OutputFolderName}
$directories = Get-ChildItem -Path $source -Directory
foreach($inputDirectory in $directories)
$djvuFiles = Get-ChildItem $inputDirectory.FullName -Filter *.djv*
$outputPath = join-path $outputDir $inputDirectory.Name
if(-not(Test-path $outputPath))
New-Item $outputPath -ItemType Directory
$djvuFiles | ForEach-Object{
$inputFileFullName = $_.FullName
$outputfileName = Join-Path $outputPath "$($_.BaseName).txt"
try {
Write-Information -MessageData "Converting file $inputFileFullName" -InformationAction Continue
djvutxt.exe $inputFileFullName $outputfileName
catch {
Write-Warning "Error while converting $inputFileFullName"
Write-Warning $_
Please feel free to reach me if you have any questions on my email. 
You may need to take help from OCR if you have image files saved in djvu, tesseractOCR can be used via commandline for the same purpose.

Saturday, March 19, 2016

Run powershell on remote machine without enabling powershell remoting !

Here is a tricky way to execute powershell code on remote machine without actually enabling powershell remoting. Enabling powershell remote lets you run powershell commands on a remote machine and get the output on to your computer.

You can either use the -ComputerName parameter available in a command or use the Invoke-Command using the -Computer parameter, or create a psremoting session and run the Invoke-Command on the session.

If PSRemoting is not enabled on your environment you do not have any way to run powershell script on a remote machine.

I was creating a remote software installation tool using powershell which wraps the PsExec tool and lets you install a software on remote machine if you know the silent installation commandlines. With a slight modification same tool can be used for running a script on the remote machine and get the outputs.

PSExec redirects the output onto your screen but if you create an output file on the remote machine you need to figure a way to get it copied onto your local machine from the remote machine, or create a share in your localmachine/server to which you can save the output from the remote machine.

Script uses the cmdkey to cache the credentials and maps the remote machine C drive to copy the setup files.

Creates a folder in C drive on the remote machine, copies the ps1 file you want to run along with a batch file which triggers the powershell script.

Once the script execution is completed on the remote machine, mapped network drive and cached credentials will be deleted.

GUI creation credit to following blog.
Download psexec and save it in the same folder where you have the main script.

The below script can be run using the remote machine name and the ps file source folder name.

Here is how your script folder should look like.

Before running the script, want to show that the remote machine has a restricted powershell execution policy and PSremoting is not enabled(Screenshot1).
Below  script would be run on this machine from a different machine.

$process = Get-Process
Write-Host "Also writing output to file on remote machine check C:\temp\lanabhat.txt"
$process | Out-File C:\temp\Lanabhat.txt
How to run the script.

Commandline entered in the installCommand.bat file
powershell -ExecutionPolicy ByPass C:\Software_Install\test-remoteShare.ps1

Running the powershell script.
.\Run-PowershellRemote.ps1 -MachineName PC0001 -installSourceFiles C:\Users\lanabhat\Desktop\Remote-Powershell\test-powershellscript

Credentials to connect to the remote machine, username would default to COMPUTARENAME\Administrator , if you have a service account or a domain admin account, you can modify the code and replace it to default to that value.

Remote machine C drive is mapped to P drive a folder is created to batch file and powershell script is copied to that folder and batch file is executed with system account.

Credential will be cached to connect to run psexec on remote machine. It will be removed at the end.

Output of the script is shown in your machine but the script is running on the remote machine.

Once the execution is completed press enter to do clean-up. Files copied to remote machine will be deleted and shared folder will be unmapped.

Here is the result from the remote machine.

And finally here is the script.

Please share your feedback and send me a message if you find it useful !

   Run powershell script on remote machine using psexec
   Copies the setup files specified in the source.
   This script runs the commandline from the installCommand.bat file on remote machine.
   Copies the setup files to remote machine
   Executes the installation.
   .\Run-PowershellScript.ps1 -MachineName "hostname1" -installSourceFiles \\sharename\foldername -Verbose
   .\Run-PowershellScript -MachineName "hostname2" -installSourceFiles \\sharename\foldername -Verbose

        #Machine Name

        #Shared folder location of setup.

#region functions
Function Get-RandomDriveLetter
$drive=ls function:[d-z]: -n|?{!(test-path $_)}|random

Function map-Drive($driveLetter,$networkDrive,$creds)
  $errorFlag = $false

  $net = new-object -ComObject WScript.Network
  Write-Verbose "Drive letter $driveLetter"
  Write-Verbose "Network share $networkDrive"

  $user_cred = $creds #Get-Credential -Message "Enter the admin cred to map C: drive" $userID

  if($user_cred -eq $null)
   "Credentials were not entered, cannot proceed"
   return 0
   $networkCred = $user_cred.GetNetworkCredential()
   $userID=$networkCred.Domain + "\" + $networkCred.UserName
   $driveLetter += ":"
    $errorFlag = $true
    $ErrorMessage = $_.Exception.Message
    if($ErrorMessage -match "The specified network password is not correct")
     Write-Host "Invalid credentials" -ForegroundColor Yellow
     Write-Host "Invalid credentials" -ForegroundColor Yellow

    exit 0


#Unmap the network drive
Function Unmap-NetworkDrive($driveLet)
  Remove-PSDrive -Name $driveLet

function Cache-Creds($hostname,$username,$password)
  cmdkey.exe /add:$hostname /user:$userName /pass:$password

function Remove-CacheCred($hostname)
cmdkey.exe /delete:$hostname

#endregion functions

$localSetup = "Software_Install" #Local folder which will be created on C drive on user machine
$batchFile = "$($Script:PSScriptRoot)\installCommand.bat" #Command file which will be copied to user machine
$remoteBatchPath = "C:\$($localSetup)\installCommand.bat"

#create a batch file as per the script argument
Write-Verbose "Commandline used is: $commandLine"

$hostname =  $MachineName
$userName = "$($hostname)\administrator"
$credential=Get-Credential $userName -Message "Please center the password to access the remote machine"
$password = $credential.GetNetworkCredential().Password;

#Create a cached credentials for psexec connection
Cache-Creds $hostname $username $password

$remMachinedriveLetter = Get-RandomDriveLetter
$sharePath = "\\$($hostname)\C$"

#Map the remote machine C drive
map-Drive $remMachinedriveLetter $sharePath $credential

#Created SCCM_Install folder on c drive, if it does not exists
if(!(Test-Path "$($remMachinedriveLetter):\$localSetup"))
Write-Verbose "Directory not found on remote server, creating the directory: $($remMachinedriveLetter):\$localSetup"
New-Item -path "$($remMachinedriveLetter):\$localSetup" -ItemType Directory

#Copy setup files to remote machine
Write-Verbose "Copying setup files from $installSourceFiles"
Copy-Item "$($installSourceFiles)\*" "$($remMachinedriveLetter):\$localSetup\" -Recurse -Verbose -Force -Container

Write-Verbose "Copying batchfile : $batchFile"
Copy-Item $batchFile "$($remMachinedriveLetter):\$localSetup\" -Verbose

#run psexec and launch install
Write-Verbose "Running PSEXEC to trigger the batch"
& .\psexec.exe \\$hostname -h -s $remoteBatchPath

Write-Host "Exitcode:" -ForegroundColor Yellow

#Unmap network drive, cleanup
Write-Verbose "Removing Cached Creds"
Remove-CacheCred $hostname

Read-Host "Press enter to do cleanup"
Write-Verbose "Deleting installation files"
Remove-Item "$($remMachinedriveLetter):\$localSetup" -Recurse -Verbose

Write-Verbose "Removing network drive"
Unmap-NetworkDrive -driveLet $remMachinedriveLetter

Coming next -

Remote software installer, merging the power of powershell and psexec....

Friday, March 18, 2016

SCCM Client DP-MP-SUP Port Query tool

A script which can help you to determine if the client machine is able to established connection to DP, MP and SUP on required ports.

A UI will be presented to user to select the site and do the port query.

Change the variable $regionsComboData and add your site names the format should be Sitename-SiteCode , as it is used later in the script .

This script uses the portquery utility to do the port query. It needs to be downloaded and kept in the same folder as the script.

Place the script inside a folder named logic, script will create the folder named Result and generate the csv file with port query results. Also it consolidate the log created by portqry tool and generate a single log file inside results folder.

Verbose output will be shown in Powershell console.

Screenshot of the UI.

To generate the CSV file which has the server details stored use the second script which is available at the end of this post, this csv file also should be placed along with the script.

#Write-Host "Setting variables" -ForegroundColor Yellow
$regionsComboData = "Site1-SI1","Site2-SI2", "Site3-SI3", "Site4-SI4"

$scriptFolder = split-path -parent $MyInvocation.MyCommand.Definition
#Write-Verbose "Script path: $scriptFolder"

$substringlen = $scriptFolder.LastIndexOf("\")
$resultFolder = "$($scriptFolder.Substring(0,$substringlen))\Result"

$consolidatedLog = "$($resultFolder)\PortQueryLog.log"
$portStatusFile = "$($resultFolder)\PortStatus.csv"

$ServerDetailsFile = "$($scriptFolder)\ServerDetails.csv"

#Write-Verbose "Log file: $consolidatedLog"
#Write-Verbose "Results file: $portStatusFile"
#Write-Verbose "Server Details file: $ServerDetailsFile"
#Write-Host " "

#region GUICode

$inputXML = @"
<Window x:Class="WpfApplication2.MainWindow"
        Title="PortChecker for SCCM Client" Height="386.386" Width="550.99">
            <ColumnDefinition Width="517*"/>
            <ColumnDefinition Width="0*"/>
            <ColumnDefinition Width="26*"/>
        <Button x:Name="btn_start" Content="Show Details" HorizontalAlignment="Left" Margin="320,34,0,283" Width="100"/>
        <ListView x:Name="listview" HorizontalAlignment="Left" Height="210" Margin="43,103,0,0" VerticalAlignment="Top" Width="464">
                    <GridViewColumn Header="ServerName" DisplayMemberBinding ="{Binding 'ServerName'}" Width="200"/>
                    <GridViewColumn Header="Port" DisplayMemberBinding ="{Binding 'Port'}" Width="60"/>
                    <GridViewColumn Header="Protocol" DisplayMemberBinding ="{Binding 'Protocol'}" Width="80"/>
                    <GridViewColumn Header="Status" DisplayMemberBinding ="{Binding 'Status'}" Width="100"/>
        <ComboBox x:Name="cmb_region" HorizontalAlignment="Left" Height="38" Margin="43,34,0,0" VerticalAlignment="Top" Width="205"/>
        <TextBlock x:Name="msgToUser" HorizontalAlignment="Left" Height="17" Margin="43,81,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="449"/>

#Checkbox GUI code
<CheckBox x:Name="chk_verbose" Content="CheckBox" HorizontalAlignment="Left" Height="26" Margin="262,34,0,0" VerticalAlignment="Top" Width="19" RenderTransformOrigin="19.316,-1.212"/>
        <Label x:Name="Verbose" Content="Verbose" HorizontalAlignment="Left" Height="32" Margin="253,49,0,0" VerticalAlignment="Top" Width="68"/>        

$inputXML = $inputXML -replace 'mc:Ignorable="d"','' -replace "x:N",'N'  -replace '^<Win.*', '<Window'
[xml]$XAML = $inputXML
#Read XAML
  $reader=(New-Object System.Xml.XmlNodeReader $xaml)
  try{$Form=[Windows.Markup.XamlReader]::Load( $reader )}
  catch{Write-Host "Unable to load Windows.Markup.XamlReader. Double-check syntax and ensure .net is installed."}
# Load XAML Objects In PowerShell
$xaml.SelectNodes("//*[@Name]") | %{Set-Variable -Name "WPF$($_.Name)" -Value $Form.FindName($_.Name)}
Function Get-FormVariables{
if ($global:ReadmeDisplay -ne $true){Write-host "If you need to reference this display again, run Get-FormVariables" -ForegroundColor Yellow;$global:ReadmeDisplay=$true}
write-host "Found the following interactable elements from our form" -ForegroundColor Cyan
get-variable WPF*

function Write-MessageToGUI($message)
$WPFmsgToUser.Text = $message
# Actually make the objects work
#Sample entry of how to add data to a field
# Shows the form
#write-host "To show the form, run the following" -ForegroundColor Cyan

#$WPFFind.Add_Click({$WPFResult.Text = Get-BoundaryDetails $WPFHostName.Text})

#endregion GUI Code

Function initialize-Settings
    if(!(Test-Path $resultFolder))
     Write-Host "Result folder not found, creating the folder" -ForegroundColor Yellow
     mkdir $resultFolder

    if(!(Test-Path $ServerDetailsFile))
     Write-Host "Unable to find the csv file with server details @ $ServerDetailsFile" -ForegroundColor Yellow
     Exit 0

    Write-Host "Checking if portqry tool is present in the directory" -ForegroundColor Yellow
    if(!(Test-Path "$($scriptFolder)\PortQry.exe"))
     #Get-ChildItem .\
     Write-Host "Portquery.exe is not found, aborting" -ForegroundColor Yellow
     Exit 0

Function Get-PortStatus($serverNameIP,$PortNo,$prtcl)
<#$serverNameIP = "C105JWWCMNSDP"
$prtcl = "TCP"
$portNo = 80

Write-Host "Querying....... Server: $serverNameIP, PortNo: $portNo , Protocol: $prtcl" -ForegroundColor Yellow

#Read-Host "test"
$logFile =  "$($scriptFolder)\$($serverNameIP)_$($prtcl)_$($portNo)_portRes.log"

& $scriptFolder\PortQry.exe -n $serverNameIP -p $prtcl -e $portNo -l $logFile -q

Write-Host "Writing data to consolidated log file" -ForegroundColor Yellow
Get-Content $logFile | Out-File $consolidatedLog -Append -Encoding ascii
Write-Host "Removing log created by proqry tool $logFile" -ForegroundColor Yellow
Remove-Item $logFile
$portStatus = "UNKNOWN"

#Write-Verbose "Exitcode $LASTEXITCODE"

  switch ($LASTEXITCODE)
  0 {$portStatus = "OPEN"}
  1 {$portStatus = "CLOSED"}
  2 {$portStatus = "FILTERED"}
  Default{$portStatus = "UNKNOWN" }

$portStatusObj = New-Object -TypeName psobject
Add-Member -InputObject $portStatusObj -MemberType NoteProperty -Name "ServerName" -Value $serverNameIP
Add-Member -InputObject $portStatusObj -MemberType NoteProperty -Name "Port" -Value $portNo
Add-Member -InputObject $portStatusObj -MemberType NoteProperty -Name "Protocol" -Value $prtcl
Add-Member -InputObject $portStatusObj -MemberType NoteProperty -Name "Status" -Value $portStatus

#Write-Verbose "Returning result"

Return $portStatusObj

#region portprofiles
$portProfiles = @()

$portProfile = New-Object -TypeName psobject
Add-Member -InputObject $portProfile -MemberType NoteProperty -Name "PortNumber" -Value "80"
Add-Member -InputObject $portProfile -MemberType NoteProperty -Name "ProtocolType" -Value "TCP"
Add-Member -InputObject $portProfile -MemberType NoteProperty -Name "ServerType" -Value "DP"


$portProfile = New-Object -TypeName psobject
Add-Member -InputObject $portProfile -MemberType NoteProperty -Name "PortNumber" -Value "443"
Add-Member -InputObject $portProfile -MemberType NoteProperty -Name "ProtocolType" -Value "TCP"
Add-Member -InputObject $portProfile -MemberType NoteProperty -Name "ServerType" -Value "DP"

$portProfile = New-Object -TypeName psobject
Add-Member -InputObject $portProfile -MemberType NoteProperty -Name "PortNumber" -Value "10123"
Add-Member -InputObject $portProfile -MemberType NoteProperty -Name "ProtocolType" -Value "TCP"
Add-Member -InputObject $portProfile -MemberType NoteProperty -Name "ServerType" -Value "MP"

$portProfile = New-Object -TypeName psobject
Add-Member -InputObject $portProfile -MemberType NoteProperty -Name "PortNumber" -Value "80"
Add-Member -InputObject $portProfile -MemberType NoteProperty -Name "ProtocolType" -Value "TCP"
Add-Member -InputObject $portProfile -MemberType NoteProperty -Name "ServerType" -Value "MP"

$portProfile = New-Object -TypeName psobject
Add-Member -InputObject $portProfile -MemberType NoteProperty -Name "PortNumber" -Value "443"
Add-Member -InputObject $portProfile -MemberType NoteProperty -Name "ProtocolType" -Value "TCP"
Add-Member -InputObject $portProfile -MemberType NoteProperty -Name "ServerType" -Value "MP"

$portProfile = New-Object -TypeName psobject
Add-Member -InputObject $portProfile -MemberType NoteProperty -Name "PortNumber" -Value "80"
Add-Member -InputObject $portProfile -MemberType NoteProperty -Name "ProtocolType" -Value "TCP"
Add-Member -InputObject $portProfile -MemberType NoteProperty -Name "ServerType" -Value "SUP"

$portProfile = New-Object -TypeName psobject
Add-Member -InputObject $portProfile -MemberType NoteProperty -Name "PortNumber" -Value "8530"
Add-Member -InputObject $portProfile -MemberType NoteProperty -Name "ProtocolType" -Value "TCP"
Add-Member -InputObject $portProfile -MemberType NoteProperty -Name "ServerType" -Value "SUP"

$portProfile = New-Object -TypeName psobject
Add-Member -InputObject $portProfile -MemberType NoteProperty -Name "PortNumber" -Value "443"
Add-Member -InputObject $portProfile -MemberType NoteProperty -Name "ProtocolType" -Value "TCP"
Add-Member -InputObject $portProfile -MemberType NoteProperty -Name "ServerType" -Value "SUP"

$portProfile = New-Object -TypeName psobject
Add-Member -InputObject $portProfile -MemberType NoteProperty -Name "PortNumber" -Value "8531"
Add-Member -InputObject $portProfile -MemberType NoteProperty -Name "ProtocolType" -Value "TCP"
Add-Member -InputObject $portProfile -MemberType NoteProperty -Name "ServerType" -Value "SUP"
#endregion portprofiles

function Start-Activity($ClientRegion)
    $selectedSiteCode = "none"
    Write-Host "Region selected: $ClientRegion" -ForegroundColor Yellow

    $startPos = $ClientRegion.IndexOf("-");
    $selectedSiteCode = $ClientRegion.Substring($startPos+1)

    Write-Host "Selected site: $selectedSiteCode" -ForegroundColor Yellow

    $csvData = Import-Csv $ServerDetailsFile
    $serversToCheck = $csvData | Where-Object{$_.SiteCode -eq $selectedSiteCode}

    #Write-Verbose "Servers to check are"
    #Write-Output $serversToCheck
    #Write-Verbose ""

    $allPortStatus = @();

    foreach($server in $serversToCheck)
     Write-MessageToGUI "Querying server: $($server.ServerName) ....."
      "DP" {
       $ports = $portProfiles | ?{$_.ServerType -eq "DP"}
       foreach($port in $ports)
        $res = Get-PortStatus $server.ServerName $port.PortNumber $port.ProtocolType
        #Write-Verbose " "
        #Write-Verbose "Adding returned output to array DP"
        $allPortStatus += $res
      "MP" {
       $ports = $portProfiles | ?{$_.ServerType -eq "MP"}
       foreach($port in $ports)
        $res = Get-PortStatus $server.ServerName $port.PortNumber $port.ProtocolType
        #Write-Verbose "Adding returned output to array MP"
        $allPortStatus += $res
      "SUP" {
       $ports = $portProfiles | ?{$_.ServerType -eq "SUP"}
       foreach($port in $ports)
        $res = Get-PortStatus $server.ServerName $port.PortNumber $port.ProtocolType
        #Write-Verbose "Adding returned output to array SUP"
        $allPortStatus += $res
    $allPortStatus | Select-Object ServerName,Port,Protocol,Status | Format-Table

    Write-Host "Writing output to file $portStatusFile" -ForegroundColor Yellow
    $allPortStatus | Select-Object ServerName,Port,Protocol,Status | Export-Csv $portStatusFile

    #Adding value to GUI
    Write-Host "Adding data to GUI" -ForegroundColor Yellow
   $allPortStatus | Select-Object ServerName,Port,Protocol,Status |  ForEach-Object {$WPFlistView.AddChild($_)}

    Write-Host "----------------------------------------------------" -ForegroundColor Green
    Write-Host "Task completed successfully" -ForegroundColor Yellow
    Write-Host "----------------------------------------------------" -ForegroundColor Green

#Write your code here
#$WPFchk_verbose.IsChecked = $true


#Populate data in combobox
$regionsComboData | ForEach-object {$WPFcmb_region.AddChild($_)}

$SiteCodeValue = $WPFcmb_region.Text
if($SiteCodeValue -notmatch "[*A-Z*]")
  Write-MessageToGUI "Please select a region"
  Return 0;
  Start-Activity $SiteCodeValue

$Form.ShowDialog() | out-null
Generate the server details csv file
Run this on CAS server and copy the csv file

$outputFile = "$($env:userprofile)\desktop\ServerDetails.csv"

function writeto-file($serverData,$file)
foreach($server in $serverData)
  Write-Output "$($server.ServerName),$($server.SiteCode),$($server.ServerType)" | Out-File $file -Encoding ascii -Append

$dps = Get-CMSite | %{Get-CMDistributionPoint -SiteCode $_.SiteCode} | Select-Object  @{Name="ServerName";Expression={($_.NetworkOSPath).subString("2");}}, SiteCode,@{Name="ServerType";Expression={"DP"};}
$mps = Get-CMSite | %{Get-CMManagementPoint -SiteCode $_.SiteCode} | Select-Object  @{Name="ServerName";Expression={($_.NetworkOSPath).subString("2");}}, SiteCode,@{Name="ServerType";Expression={"MP"};}
$sups = Get-CMSite | %{Get-CMSoftwareUpdatePoint -SiteCode $_.SiteCode} | Select-Object  @{Name="ServerName";Expression={($_.NetworkOSPath).subString("2");}}, SiteCode,@{Name="ServerType";Expression={"SUP"};}

cd C:

"ServerName,SiteCode,ServerType" | Out-File $outputFile -Encoding ascii

writeto-file $dps $outputFile
writeto-file $mps $outputFile
writeto-file $sups $outputFile