Gheek.net

September 11, 2015

Use Perl to Create Multi-page or Multi-Document Visio(s) using Visio Plugin for PowerShell

Filed under: perl, PowerShell, Visio — lancevermilion @ 12:36 pm

I draw Visio diagram very regular but have been very motivated to create more and more dynamic diagram that only require the artistic touch to make the diagrams look the way you want. There should be no reason to waste time typing in data if you already have it in a Excel Spreadsheet, SharePoint Foundation List, or DB. I am using Excel Spreadsheets for this example but you can use whatever you want. There is additional things you would need to do to make it work obviously. To do all the drawing you can do it in VBA, PowerShell using the Visio module/plugin, or C# (I will learn this one day). I have decided to use the Visio module/plugin for PowerShell by Saveen Reddy because it is the quickly to learn and use for me. He is the author of a larger project call Visio Automation.

I did create my own Stencil Pack and added my Data Graphics to it. I prefer to create diagram that has simple hostnames (that are unique) so they can be linked using the external data concept in Visio and use the Excel Spreadsheet, SharePoint Foundation List, or DB options. Since I use the unique hostname as the key in the XLS I can match that to the name of the shape on the page. I can then populate the Data Graphic with all the data from the external data. I also keep a connection list in a format of C followed by any number of digits. This allows the WAN links to be linked to the external data.

Here is a little of useful powershell to quickly select the specific shapes all at once.

# Select all rectangles (aka didn't match criteria for specific stencil)
Select-VisioShape None
$shapes = Get-VisioShape *
$shapeids = $shapes | where { $_.NameU -like "*rectangle*"} | Select ID
if ($shapeids) { Select-VisioShape $shapeids.ID }

# Select all Dynamic connectors
Select-VisioShape None
$shapes = Get-VisioShape *
$shapeids = $shapes | where { $_.NameU -like "*Dynamic connector*"} | Select ID
if ($shapeids) { Select-VisioShape $shapeids.ID }

Resize a rectangle shape (default shape in the code that draws the diagrams automatically) on all pages

#
# Go page by page and resize each rectangle and set other page attributes
#

# If the Visio plugin for PowerShell doesn't see a connection to Visio connect to it.
if (-Not (Test-VisioApplication) ) 
{
    Connect-VisioApplication
}

# Sometimes you will need to also attach to the document.
$app = Get-VisioApplication


$pages = Get-VisioPage | where { $_.Name } | Select Name
foreach ( $page in $pages )
{
    if ( "background" -eq $page.Name ) {break}
    Write-Host Processing: $page.Name
    Set-VisioPage -Name $page.Name
    $shapes = Get-VisioShape *
    $rectids = $shapes | where { $_.NameU -like "*rectangle*"} | Select ID
    if ($rectids)
    {
        Write-Host $rectids.ID
        Select-VisioShape $rectids.ID
        Set-VisioShapeCell -Width 0.6049 -Height 0.475
        Set-VisioPageLayout -Orientation Landscape -BackgroundPage background
        Set-VisioPageCell -PageWidth 11.0 -PageHeight 8.5
    }
    Select-VisioShape None
}

Because I am not a decent PowerShell programmer/script/hacker/etc I wrote a Perl script to output a PowerShell script to put in PowerShell ISE and do my dirty work for me. 🙂

My Script requires input from a file in the following format.

label = "Name of Page/Document in Visio"
DEV1,DEV2,Connector_Label,Connector_Type

Real Data

label = "Site A"
ATTCLOUD,DEV1,Connector_Label,Connector_Type
DEV1,DEV2,Connector_Label,Connector_Type
DEV2,DEV3,Connector_Label,Connector_Type
DEV2,DEV5,Connector_Label,Connector_Type
DEV2,DEV4,Connector_Label,Connector_Type
XOCLOUD,DEV4,Connector_Label,Connector_Type

Currently Connector_Label is ignored but could be used for different implementations.
Connector Types are: Straight, RightAngle, or Curved (this is the default)

Perl Script to Create a PowerShell Script to Dynaimcally draw Diagrams

#!/usr/local/bin/perl

my $filename = $ARGV[0];
my @arr = do {
    open my $fh, "<", $filename
        or die "could not open $filename: $!";
    <$fh>;
};

my $hash = {};
my $save_location = 'c:';

# Where to place each diagram
# 0 = Each Diagram goes on new page
# 1 = Each Diagram goes in its own Visio file
my $newdocper = 0;

my $site = '';
my $site_clean = '';
my $cnt = 0;
for my $line (@arr)
{
  chomp($line);
  if ( $line =~ /label/ )
  {
    $site = '';
    (undef, $site) = split(/ = /, $line);
    $site_clean = $site;
    $site_clean =~ s/"//g;
    $site_clean =~ s/-/_/g;
    $site_clean =~ s/#/NUM/g;
    $site_clean =~ s/&/_/g;
    $site_clean =~ s/\(//g;
    $site_clean =~ s/\)//g;
    $site_clean =~ s/\//_/g;
    $site_clean =~ s/ /_/g;
    $site_clean =~ s/\./_/g;
  }
  else
  {
    $cnt++;
    my ($from, $to, $label, $linetype) = split(/,/, $line);
    my $to_clean = $to;
    $to_clean =~ s/"//g;
    $to_clean =~ s/-/_/g;
    $to_clean =~ s/#/NUM/g;
    $to_clean =~ s/&/_/g;
    $to_clean =~ s/\(//g;
    $to_clean =~ s/\)//g;
    $to_clean =~ s/\//_/g;
    $to_clean =~ s/ /_/g;
    $to_clean =~ s/\./_/g;
    my $to_normal = $to;
    my $from_clean = $from;
    $from_clean =~ s/"//g;
    $from_clean =~ s/-/_/g;
    $from_clean =~ s/#/NUM/g;
    $from_clean =~ s/&/_/g;
    $from_clean =~ s/\(//g;
    $from_clean =~ s/\)//g;
    $from_clean =~ s/\//_/g;
    $from_clean =~ s/ /_/g;
    $from_clean =~ s/\./_/g;
    my $from_normal = $from;

    # Build Node portion of hash
    $hash->{$site_clean}->{'nodes'}->{$from}->{'clean'} = $from_clean;
    $hash->{$site_clean}->{'nodes'}->{$from}->{'normal'} = $from_normal;
    $hash->{$site_clean}->{'nodes'}->{$to}->{'clean'} = $to_clean;
    $hash->{$site_clean}->{'nodes'}->{$to}->{'normal'} = $to_normal;

    # Build links portion of hash
    $hash->{$site_clean}->{'links'}->{$cnt}->{'from'} = $from_clean;
    $hash->{$site_clean}->{'links'}->{$cnt}->{'to'} = $to_clean;
    $hash->{$site_clean}->{'links'}->{$cnt}->{'label'} = $label;
    $hash->{$site_clean}->{'links'}->{$cnt}->{'linetype'} = $linetype;
    
  }
}

print "Set-StrictMode -Version 2\n";
print "\$ErrorActionPreference = \"Stop\"\n";
print "Import-Module Visio\n";
print "\$options = New-Object VisioAutomation.Models.DirectedGraph.MSAGLLayoutOptions\n";
print "\$d = New-VisioDirectedGraph\n";
print "\$app = New-Object -ComObject Visio.Application \n";
print "\$app.visible = \$true \n";
print "\$docs = \$app.Documents \n";
print "\$doc = \$docs.Add(\"DTLNET_U.vst\") \n";
print "\$pages = \$app.ActiveDocument.Pages \n";
print "\$page = \$pages.Item(1)\n";
print "\$stencil = \$app.Documents.Add(\"My_Network_Stencil_Pack.vss\")\n";
print "\$backgroundborder = \$stencil.Masters.Item(\"Background Border\")\n";
print "\$infobar = \$stencil.Masters.Item(\"Info Bar on Background\")\n";
print "\$page.Name = \"background\"\n";
print "\$page.AutoSize = \$false\n";
print "\$page.Background = \$true\n";
print "\$page.Document.PrintLandscape = \$true\n";
print "\$page.document.PrintFitOnPages = \$true\n";
print "\$bg = \$page.Drop(\$backgroundborder, 5.5, 4.25) \n";
print "\$bginfobar = \$page.Drop(\$infobar, 8.0646, 0.95) \n";
print "\$page.CenterDrawing\n";
print "if (-Not (Test-VisioApplication) ) \n";
print "{\n";
print "    Connect-VisioApplication\n";
print "}\n";
print "if (-Not (Test-VisioDocument) )\n";
print "{\n";
print "    Set-VisioDocument -Name Drawing1\n";
print "}\n";
print "Set-VisioPageCell -PageWidth 11.0 -PageHeight 8.5\n";
for my $sitekey ( sort keys %$hash )
{
  print "\$d = New-VisioDirectedGraph\n";
  #my $nodecnt = 1;
  #my @custprops = ();
  # Print out all Nodes per Site
  for my $node ( sort keys %{$hash->{$sitekey}->{'nodes'}} )
  {
    my $node_label = $hash->{$sitekey}->{'nodes'}->{$node}->{'normal'};
    my $node_name = $hash->{$sitekey}->{'nodes'}->{$node}->{'clean'};
    my $node_stencil = "BASIC_U.VSS";
    my $node_shape = "Rectangle";
    my $node_stencil_my = "My_Network_Stencil_Pack.vss";
    my $node_shape_l2switch = "Cisco L2 Switch DG";
    my $node_shape_l3switch = "Cisco L3 Switch DG";
    my $node_shape_rtr = "Cisco Router DG";
    my $node_shape_fw = "Cisco ASA 5500 Series DG";
    my $node_shape_cloud = "Cloud";
    my $node_shape_rectangle = "Rectangle DG";
    my $nodename = "\$$node_name = \$d.AddShape(\"$node_name\",\"$node_label\", \"$node_stencil\", \"$node_shape\")\n";
    #my $nodename = "\$$node_name = \$d.AddShape(\"$node_name\",\"$node_label\", \"$node_stencil_my\", \"$node_shape_rectangle\")\n";
    $nodename = "\$$node_name = \$d.AddShape(\"$node_name\",\"$node_label\", \"$node_stencil_my\", \"$node_shape_cloud\")\n" if ( $node_name =~ /qmoe/i );
    $nodename = "\$$node_name = \$d.AddShape(\"$node_name\",\"$node_label\", \"$node_stencil_my\", \"$node_shape_l3switch\")\n" if ( $node_name =~ /3850/ );
    $nodename = "\$$node_name = \$d.AddShape(\"$node_name\",\"$node_label\", \"$node_stencil_my\", \"$node_shape_l3switch\")\n" if ( $node_name =~ /6500/ );
    $nodename = "\$$node_name = \$d.AddShape(\"$node_name\",\"$node_label\", \"$node_stencil_my\", \"$node_shape_l2switch\")\n" if ( $node_name =~ /as/ );
    $nodename = "\$$node_name = \$d.AddShape(\"$node_name\",\"$node_label\", \"$node_stencil_my\", \"$node_shape_rtr\")\n" if ( $node_name =~ /wr\d\d/i );
    $nodename = "\$$node_name = \$d.AddShape(\"$node_name\",\"$node_label\", \"$node_stencil_my\", \"$node_shape_rtr\")\n" if ( $node_name =~ /vrf_/i );
    $nodename = "\$$node_name = \$d.AddShape(\"$node_name\",\"$node_label\", \"$node_stencil_my\", \"$node_shape_rtr\")\n" if ( $node_name =~ /rt\d\d/i );
    $nodename = "\$$node_name = \$d.AddShape(\"$node_name\",\"$node_label\", \"$node_stencil_my\", \"$node_shape_fw\")\n" if ( $node_name =~ /fw\d\d/i );
    print $nodename;
  }

  for my $linknum ( sort keys %{$hash->{$sitekey}->{'links'}} )
  {
    # Print out all links per Site
    my $linknum_ = "C" . $linknum;
    my $from_ = $hash->{$sitekey}->{'links'}->{$linknum}->{'from'};
    my $to_ = $hash->{$sitekey}->{'links'}->{$linknum}->{'to'};
    my $label_ = $hash->{$sitekey}->{'links'}->{$linknum}->{'label'};
    my $linetype_ = $hash->{$sitekey}->{'links'}->{$linknum}->{'linetype'};
    print "\$d.AddConnection(\"$linknum_\",\$$from_,\$$to_,\"$linknum_\",\"$linetype_\")\n";
  }
  print "New-VisioDocument\n" if ( $newdocper );
  print "\$p = New-VisioPage -Name \"$sitekey\"  -Height 8.5 -Width 11\n" if ( ! $newdocper );
  print "\$d.Render(\$p,\$options)\n";
  print "\$shapes = Get-VisioShape *\n";
  print "\$rectids = \$shapes | where { \$_.NameU -like \"*rectangle*\"} | Select ID\n";
  print "if (\$rectids)\n";
  print "{\n";
  print "    Select-VisioShape \$rectids.ID\n";
  print "    Set-VisioShapeCell -Width 0.6049 -Height 0.475\n";
  print "    Set-VisioPageLayout -Orientation Landscape -BackgroundPage background\n";
  print "    Set-VisioPageCell -PageWidth 11.0 -PageHeight 8.5\n";
  print "}\n";
  print "Select-VisioShape None\n";

  print "Save-VisioDocument \"$save_location\\$sitekey.vsd\"\n" if ( $newdocper );
  print "\$od = Get-VisioDocument -ActiveDocument\n" if ( $newdocper );
  print "Close-VisioDocument -Documents \$od\n" if ( $newdocper );
}

Example of Generated PowerShell

Set-StrictMode -Version 2
$ErrorActionPreference = "Stop"
Import-Module Visio
$options = New-Object VisioAutomation.Models.DirectedGraph.MSAGLLayoutOptions
$d = New-VisioDirectedGraph
$app = New-Object -ComObject Visio.Application 
$app.visible = $true 
$docs = $app.Documents 
$doc = $docs.Add("DTLNET_U.vst") 
$pages = $app.ActiveDocument.Pages 
$page = $pages.Item(1)
# You would have to comment out the following three lines since you likely don't have them in a Stencil Pack like I do.
$stencil = $app.Documents.Add("My_Network_Stencil_Pack.vss")
$backgroundborder = $stencil.Masters.Item("Background Border")
$infobar = $stencil.Masters.Item("Info Bar on Background")
$page.Name = "background"
$page.AutoSize = $false
$page.Background = $true
$page.Document.PrintLandscape = $true
$page.document.PrintFitOnPages = $true
# You would have to comment out the following two lines since you likely don't have them in a Stencil Pack like I do.
$bg = $page.Drop($backgroundborder, 5.5, 4.25) 
$bginfobar = $page.Drop($infobar, 8.0646, 0.95) 
$page.CenterDrawing
if (-Not (Test-VisioApplication) ) 
{
    Connect-VisioApplication
}
if (-Not (Test-VisioDocument) )
{
    Set-VisioDocument -Name Drawing1
}
Set-VisioPageCell -PageWidth 11.0 -PageHeight 8.5
$d = New-VisioDirectedGraph
$ATTCLOUD = $d.AddShape("ATTCLOUD","ATTCLOUD", "BASIC_U.VSS", "Rectangle")
$DEV1 = $d.AddShape("DEV1","DEV1", "BASIC_U.VSS", "Rectangle")
$DEV2 = $d.AddShape("DEV2","DEV2", "BASIC_U.VSS", "Rectangle")
$DEV3 = $d.AddShape("DEV3","DEV3", "BASIC_U.VSS", "Rectangle")
$DEV4 = $d.AddShape("DEV4","DEV4", "BASIC_U.VSS", "Rectangle")
$DEV5 = $d.AddShape("DEV5","DEV5", "BASIC_U.VSS", "Rectangle")
$XOCLOUD = $d.AddShape("XOCLOUD","XOCLOUD", "BASIC_U.VSS", "Rectangle")
$d.AddConnection("C1",$ATTCLOUD,$DEV1,"C1","Straight")
$d.AddConnection("C2",$DEV1,$DEV2,"C2","Straight")
$d.AddConnection("C3",$DEV2,$DEV3,"C3","Straight")
$d.AddConnection("C4",$DEV2,$DEV5,"C4","Straight")
$d.AddConnection("C5",$DEV2,$DEV4,"C5","Straight")
$d.AddConnection("C6",$XOCLOUD,$DEV4,"C6","Straight")
$p = New-VisioPage -Name "Site_A"  -Height 8.5 -Width 11
$d.Render($p,$options)
$shapes = Get-VisioShape *
$rectids = $shapes | where { $_.NameU -like "*rectangle*"} | Select ID
if ($rectids)
{
    Select-VisioShape $rectids.ID
    Set-VisioShapeCell -Width 0.6049 -Height 0.475
    Set-VisioPageLayout -Orientation Landscape -BackgroundPage background
    Set-VisioPageCell -PageWidth 11.0 -PageHeight 8.5
}
Select-VisioShape None

Here is a picture of what it would draw.

Dummy_Net_Diagram

Create a free website or blog at WordPress.com.