<#  
  [PSRoomSighing2021.ps1]

  *****************************************************************
  *** Pay attention, script needs configuration in the script.  ***
  *** Read the instructions that came with this script or check ***
  *** the website at https://www.microcloud.nl for information. ***
  *****************************************************************
  
  Revision History
   20211126 - Change StringArray to filebytes conversion method in step 4 to better support special characters.
  
#>

# Environment
$SaveOnAzureAppService=$false # When True file is saved in App Service, otherwise it is saved on local path (Windows Based Servers)
# /Environment

# Azure Environment 
$AzureAppServiceRelativePath  = "rs01" # relative path to App Service URL, do not enter any / chars
$AzureAppServiceName          = "webapp2021"
$AzureAppServiceResourceGroup = "rs-psroomsigning"
# /Azure Environment

# Windows Environment
$WindowsBasedLocation = "C:\inetpub\wwwroot\rs1\default.htm"
# /Windows Environment

# SPN Info
$TenantID="XXXXXXXX-YYYY-ZZZZ-XXXX-XXXXYYYYZZZZ"
$AppID="11111111-2222-3333-4444-555566667777"
$Thumb="1234567890ABCDEF1234567890ABCDEF12345678"
# /SPN Info

# Exchange Online Room Mailboxes.
$rooms="meetingroom1@domain.com","meetingroom2@domain.com" # Enter the UPNs of the Room mailboxes
#
$htmlbackgroundpicturefilename="psroomsigning_bg.png"
#
$enableHTMLclock=$true        # Enable HTML clock
$clockalign="right"           # HTML Clock alignment  Allowed values: right,left,center
$clockshowseconds=$false      # Show seconds on clock true: HH:mm.ss - false: HH:mm
# Colors
$HTMLClockColor="848284"      # HTML Clock text color default color: 848284
$Itembackgroundcolor="848284" # Item level background color default: 848284
$RoomNameColor="000000"       # Item level background color default: 000000
$ItemColor="FFFFFF"           # Item level background color default: FFFFFF
$DateStampColor="000000"      # Item level background color default: 000000
# /Colors
$maxcalendaritems=9           # Maximum number of items on the screen.
$maxsubjectlength=53          # Truncates the subject on maximum chars
$skiplines=3                  # Add empty lines from top of screen to align with background picture.
$datestamp=$true              # Add the date on the bottom of the screen.
$ShowSnapshotTimeStamp=$false # Adds a timestamp when the snapshot/html file was created, designed for troubleshooting but can be used in production also.
#
$timezoneID="W. Europe Standard Time"  # Use this command to list alle available timezones: get-timezone -ListAvailable copy the ID in this line.
$CultureID ="nl-NL"                    # Use this command to list all available cultures, put the info from the NAME column in this variable: [System.Globalization.CultureInfo]::GetCultures([System.Globalization.CultureTypes]::AllCultures)
# /Generic settings

# Init (No intended configurable parameters here)
$TimeStampUTC = (Get-Date).ToUniversalTime()
$tz = [System.TimeZoneInfo]::FindSystemTimeZoneById($timezoneID) 
$CurrentTime = [System.TimeZoneInfo]::ConvertTimeFromUtc($TimeStampUTC, $tz)
$culture=[CultureInfo]$CultureID
$nicetoday = $CurrentTime.ToString("d MMMM yyyy",$culture) # Culture parameter also changes the language of the date stamp
$snapshottimestamp=$CurrentTime.ToString("HH:mm.ss")       # Localized timestamp
$CalendarItems=$null
$CalendarItems=@()
$FinalSortedCalendarItems=$null
$FinalSortedCalendarItems=@()
$xToday = $TimeStampUTC.ToString("yyyy-MM-dd")
$maxsubjectlength-- # Decrease 1 to adjust for the 0.
# /Init (No intended configurable parameters here)

# Step 1: Login
Connect-MgGraph -ClientID $AppID -TenantId $TenantID -CertificateThumbprint $Thumb # Connect to Microsoft Graph.
# /Step 1: Login

# Step 2: Gather data
Foreach ($room in $rooms) # Process all rooms, put Graph data in new array for processing.
  {
    Write-host "Reading room: $room"
    $xRoom=get-mguser -UserId $room # Read user object via Graph
    $xRoomCal=get-mgusercalendar -UserId $($xRoom.id) # Save the ID of the user object.    
    #
    $StartOfDay=(get-date -Hour 0 -Minute 0 -Second 1).ToUniversalTime() # start of today
    $EndOfDay=(get-date -Hour 23 -Minute 59 -Second 59).ToUniversalTime() # end of today    
    $xRoomToday=Get-MgUserCalendarView -UserId $($xRoom.id) -CalendarId $($xRoomCal.id) -all -StartDateTime $StartOfDay -EndDateTime $EndOfDay # Read All events in today's view.    
    #
    Foreach ($xxRoom in $xRoomToday) # Proces all events.
      {
        $CalendarItem = new-object psobject            
        $CalendarItem | add-member noteproperty roomname $null       
        $CalendarItem | add-member noteproperty starttime $null   
        $CalendarItem | add-member noteproperty endtime $null   
        $CalendarItem | add-member noteproperty MeetingTime $null   
        $CalendarItem | add-member noteproperty Organiser $null   
        $CalendarItem | add-member noteproperty Subject $null         
        $CalendarItem.Roomname=$xRoom.DisplayName       
        $CalendarItem.StartTime=([datetime]$xxRoom.start.DateTime)
        $CalendarItem.EndTime=([datetime]$xxRoom.end.DateTime)       
        $CalendarItem.MeetingTime=([System.TimeZoneInfo]::ConvertTimeFromUtc($CalendarItem.StartTime,$tz)).tostring("HH:mm")+" - "+([System.TimeZoneInfo]::ConvertTimeFromUtc($CalendarItem.EndTime,$tz)).tostring("HH:mm") # Meeting time for in HTML localized.
        $CalendarItem.Organiser=$xxRoom.Organizer.EmailAddress.name
        $CalendarItem.Subject=$xxRoom.Subject[0..$maxsubjectlength] -join "" # Truncate subject if longer the $maxsubjectlength     
        $NowTimeStampUTC =(Get-Date).ToUniversalTime()     # Read time every run, some parts of the script can run long time.
        If (($CalendarItem.EndTime) -gt $NowTimeStampUTC ) # If the endtime of the meeting is passed then do not re-add the item on the display.
          { # Meeting busy or in the future, add to the array.
           $CalendarItems+=$CalendarItem           
          }      
      } # /Foreach ($xxRoom in $xRoomToday)
  } # /Foreach ($room in $rooms)

$SortedCalendarItems=$CalendarItems | Sort-Object -Property starttime # Sort calendar items on Start date.

# Max out number of items in table.
$NrCalendarItems=0 # Counter to 0
Foreach ($sci in $SortedCalendarItems) 
  {
    If ($NrCalendarItems -lt $maxcalendaritems) # If more items on list that maximum allowed skip adding them to final list.
      { 
        $FinalSortedCalendarItems+=$sci
        $NrCalendarItems++ # Increase counter
      }      
  }

# /Step 2: Gather data

# Step 3: Format data
# Creat HTML Body.
$a=New-Object System.Collections.ArrayList 
$a.add("<html>") #<body>")

If ($enableHTMLclock) # Clock part 1/2
  {
    $a.add('<body onload="startTime()">')
    $a.add('<script>')
    $a.add('function startTime() {')
    $a.add('const today = new Date();')
    $a.add('let h = today.getHours();')
    $a.add('let m = today.getMinutes();')
    $a.add('let s = today.getSeconds();')
    $a.add('m = checkTime(m);')
    $a.add('s = checkTime(s);')

    # Construct Clock HTML code
    If ($clockshowseconds) { $xSeconds="+ '.' + s" } else { $xSeconds="" }
    $xClockString="" 
    Switch ($clockalign.ToLower())
    {
      "left"   { $xClockString="'&nbsp&nbsp' + h + ':' + m "+$xSeconds }
      "center" { $xClockString="h + ':' + m "+$xSeconds  } 
      "right"  { $xClockString="h + ':' + m "+$xSeconds+"+ '&nbsp&nbsp'" } 
    }
    $a.add("document.getElementById('txt').innerHTML = "+$xClockString+";")    
    # /Construct Clock HTML code
    
    $a.add('setTimeout(startTime, 1000); }')
    $a.add('function checkTime(i) {')
    $a.add('if (i < 10) {i = "0" + i};  // add zero in front of numbers < 10')
    $a.add('  return i;}')
    $a.add('</script>')
  }


$a.add('<meta http-equiv="refresh" content="30" />')
$a.add("<style>")
$a.add("body { background-image: url('"+$htmlbackgroundpicturefilename+"'); }")
$a.add("</style>") 

If ($enableHTMLclock) # Clock part 2/2
  {
    $a.add('<h1 style="font-family:Segoe UI;font-size:32px;color:'+$HTMLClockColor+';" align="'+$clockalign+'" id="txt"></h1>')
  }

$a.add("&nbsp")

# Add empty lines in HTML code to align first line.
For ($i=1; $i -le $skiplines; $i++) { $a.add("<P STYLE='text-align: right;'><font size=8>&nbsp&nbsp&nbsp&nbsp&nbsp</font></P>") }  
#
# Table width are configurable here.
$a.add('<table cellspacing="0" cellpadding="0" margin-left:20px; margin-right:20px; style="font-family:"Segoe UI", Courier, monospace; font-size:100%"; width=100%') 
$a.add("<colgroup>")
$a.add("<col span='1' style='width: 5%;'>")    # First empty column (to align left side)
$a.add("<col span='1' style='width: 20%;'>")   # Meeting room name column
$a.add("<col span='1' style='width: 20%;'>")   # Timespan column 
$a.add("<col span='1' style='width: 30%;'>")   # Subject column
$a.add("<col span='1' style='width: 20%;'>")   # Organiser column
$a.add("<col span='1' style='width: 5%;'>")    # Last empty column (to align right side)
$a.add("</colgroup>")
# /Table width are configurable here.
#
Foreach ($CI in $FinalSortedCalendarItems) # Add the events in the html/table.
  {
    $a.add("<tr>") # Adding one row in the table with the actual content
    $a.add("<td align='left'><p style='font-family:Segoe UI;font-size:32px;'>&nbsp</p></td>")
    $a.add("<td align='left'><p style='font-family:Segoe UI;font-size:32px;color:#$RoomNameColor;'>&nbsp$($CI.Roomname)</p></td>")    
    $a.add("<td align='left'><p style='font-family:Segoe UI;font-size:32px;color:#$ItemColor;background-color:#$Itembackgroundcolor'> &nbsp$($CI.MeetingTime)  </p></td>")
    $a.add("<td align='left'><p style='font-family:Segoe UI;font-size:32px;color:#$ItemColor;background-color:#$Itembackgroundcolor'> &nbsp$($CI.Subject)  </p></td>")
    $a.add("<td align='left'><p style='font-family:Segoe UI;font-size:32px;color:#$ItemColor;background-color:#$Itembackgroundcolor'> &nbsp$($CI.Organiser)  </p></td>")    
    $a.add("<td align='left'><p style='font-family:Segoe UI;font-size:32px;'>&nbsp </p></td>")    
    $a.add("</tr>")
    $a.add("<tr>") # Adding one empty row in the table for spacing between the items.
    $a.add("<td><p style='font-family:Segoe UI;font-size:14px;'>&nbsp</p></td>")    
    $a.add("<td><p style='font-family:Segoe UI;font-size:14px;'>&nbsp</p></td>")    
    $a.add("<td><p style='font-family:Segoe UI;font-size:14px;'>&nbsp</p></td>")    
    $a.add("<td><p style='font-family:Segoe UI;font-size:14px;'>&nbsp</p></td>")    
    $a.add("<td><p style='font-family:Segoe UI;font-size:14px;'>&nbsp</p></td>")    
    $a.add("<td><p style='font-family:Segoe UI;font-size:14px;'>&nbsp</p></td>")    
    $a.add("</tr>")        
  }

# Datestamp
$a.add("</head><body>")
$a.add('<div style="position: relative">')
If ($datestamp) # Add the datestamp if $true
  {
    $a.add("<p style='position: fixed; bottom: 0; width:100%; text-align: center;font-family:Segoe UI;font-size:36px;color:#$DateStampColor;'>$($nicetoday)</p>")
  }
If ($ShowSnapshotTimeStamp) # Add the SnapshotTimeStamp if $true
  {
    $a.add("<p style='position: fixed; bottom: 0; width:100%; text-align: center;font-family:Segoe UI;font-size:10px;'><i>Snapshot time: $($SnapshotTimeStamp)</i></p>")
  }
$a.add("</div> </body>")
# /Datestamp
$a.add("</table></body></html>") # Finalize html body.
# /Step 3: Format data

# Step 4: Export data
If ($SaveOnAzureAppService) 
  { # Save to Azure App Service
    Add-AzAccount -TenantId $TenantID -ApplicationId $AppID -CertificateThumbprint $Thumb # Connect to Azure (Az powershell) with same SPN
    # Convert $a variable to 'filebytes' that can be upoaded to Azure Web Service
    $fileBytes=$null
    $a | out-file "tempfile.txt" # 20211126 - Change StringArray to filebytes conversion method in step 4 to better support special characters.
    $filebytes=Get-Content "tempfile.txt" -Encoding Byte # 20211126 - Change StringArray to filebytes conversion method in step 4 to better support special characters. 
    # Get publishing profile for the web app
    $xml = [xml](Get-AzWebAppPublishingProfile -Name $AzureAppServiceName -ResourceGroupName $AzureAppServiceResourceGroup -OutputFile null)
    # Extract connection information from publishing profile
    $username = $xml.SelectNodes("//publishProfile[@publishMethod=`"FTP`"]/@userName").value
    $password = $xml.SelectNodes("//publishProfile[@publishMethod=`"FTP`"]/@userPWD").value
    $url = $xml.SelectNodes("//publishProfile[@publishMethod=`"FTP`"]/@publishUrl").value
    # Upload file 
    $xURI=$url+"/"+$AzureAppServiceRelativePath+"/default.htm"
    $uri = New-Object System.Uri($xURI)
    $request = [System.Net.FtpWebRequest]([System.net.WebRequest]::Create($uri))
    $request.Method = [System.Net.WebRequestMethods+Ftp]::UploadFile
    $request.Credentials = New-Object System.Net.NetworkCredential($username,$password)
    # Enable SSL for FTPS. Should be $false if FTP.
    $request.EnableSsl = $true;
    # Write the file to the request object.
    # $fileBytes = [System.IO.File]::ReadAllBytes($filePath)
    $request.ContentLength = $fileBytes.Length;
    $requestStream = $request.GetRequestStream()
    try {
      $requestStream.Write($fileBytes, 0, $fileBytes.Length)
    }
    finally {
      $requestStream.Dispose()
    }
    Write-Host "Uploading to $($uri.AbsoluteUri)"
    try {
       $response = [System.Net.FtpWebResponse]($request.GetResponse())
       Write-Host "Status: $($response.StatusDescription)"
      }
    finally {
      if ($null -ne $response) {
        $response.Close()
        }
    }    
  } # /Save to Azure App Service
    else
  { # Save to Windows path
    # Save to Windows based location
    $a | Out-file $WindowsBasedLocation  -Force
  }

# /Step 4: Export data


