Comparing Server IDs in two tools via PowerShell script - powershell

I am making a PowerShell script that is supposed to retrieve and compare the server IDs in two tools that we are using - Octopus tool and MOSS (the idea is to check that all servers in Octopus are also registered in MOSS). The Octopus is accessed on PowerShell via MySQL query and the MOSS is accessed via API call. Currently I am able to retrieve successfully the sql query and format it to JSON to be able to be readable by MOSS. However, I am not aware as to how to make the check if the server IDs are also present in the MOSS. All that the script does is retrieve the IDs from the Octopus SQL and then parse them to JSON and make an empty call to MOSS. Would highly appreciate it if anyone knows how to make MOSS calls from PowerShell.
The current script is:
# Extract RDS and servers from Octopus
# Define log file path
$date = $(get-date).tostring()
$currentdate = get-date -format yyyy-MM-dd
$log_file_path = "C:\Program Files\test\logs\"+$currentdate+"_extract_rds_and_servers_from_octopus.log"
$errorlog_file_path = "C:\Program Files\test\logs\errors\errors.log"
# 0. Exclude Admin Users before getting the RDS licenses which need to be reported
#& ((Split-Path $MyInvocation.InvocationName) + "\exclude_users.ps1") -log_file_path $log_file_path -errorlog_file_path $errorlog_file_path
# 1. Extract ObjectID from Octopus API for current month for each RDP user
try {
$month = (Get-Date -UFormat "%Y%m")
$UrlHost = "https://octopus.mos-windows.eu01.stackit.cloud/api/workspace/55cd5c70-d188-4ac3-b946-f1afec8764ad/report/licensing/spla-usage-reseller?&payload[month_id]=$month&payload[with_itemized]=1&_format=json&_token=j8FE4wZDmBITewHUc7lyYeX9XVVjt3dqz0ID4S6A9KQjkMeKfO7_EcgV7Qshuuw1&_tenant=TlXcM&_language=en&payload[flat_structure]=1"
$HostResponse = Invoke-RestMethod -Uri $UrlHost -Method Get
$users = $HostResponse.itemized
$objectids = #()
foreach ($user in $users.PSObject.Properties) {
if ($user.Value.readable_label -eq "Windows Server Remote Desktop Services")
{
$objectid = $user.Value.object_id
$objectids += $objectid
}
}
}
catch {
$date+" - Error in Octopus API Call: "+$_ | Out-File -Append $errorlog_file_path
exit
}
# 2. Get access device ids from Octopus Database
Import-Module -Name "C:\Program Files\test\request_database.ps1" -ArgumentList $log_file_path, $errorlog_file_path -Verbose
$get_users_devices_query =
#"
select user_id, access_device_ids from oc_reporter.ws_installed_software where user_id is not null;
"#
$ui_ad = execute_db_query $get_users_devices_query
$access_device_ids = $users_devices.access_device_ids
$user_ids = $users_devices.user_id
# 3. Get all openstack server id from Octopus Database 
$get_access_device_server_ids_query =
#"
select id, lower(SUBSTRING_INDEX(SUBSTRING_INDEX(ref_id, "-", -6), "-", 5)) as server_id, ref_id, label from ws_device
where type_id = "vm" and operating_system like "%Windows%" and created > 1644820951;
"#
$ad_si = execute_db_query $get_access_device_server_ids_query
# 4. Map the users/objectids with access device ids and server ids
# Create array with UserID filtered by ObjectID and map each AccessDeviceID(s) to corresponding ServerID
try {
$filteredsi = #()
foreach ($userid in $ui_ad)
{
if ($objectids -contains $userid.user_id)
{
$filteredad = $userid
foreach ($id in $ad_si)
{
if ($filteredad.access_device_ids.split(',') -contains $id.id)
{
$filteredsi += [PSCustomObject]#{"userid" = $filteredad.user_id; "serverid" = $id.server_id}
}
}
}
}
}
catch {
$date+" - Error in Mapping userIDs/objectIDs/accessdeviceIDs/serverIDs: "+$_ | Out-File -Append $errorlog_file_path
exit
}
# Preparation for MOSS
# Create JSON contentblock with looped $filteredsi array
try {
$myArray = $filteredsi
$uniqueUsers = [System.Collections.ArrayList]::new()
for($i = 0; $i -lt $myArray.Count; $i++){
if(!$uniqueUsers.Contains($myArray[$i].userid)){
$uniqueUsers.Add($myArray[$i].userid)
}
}
$allMappings = [System.Collections.ArrayList]::new()
for($i = 0; $i -lt $uniqueUsers.Count; $i++){
$singleMapping = [PSCustomObject]#{id = $uniqueUsers[$i]; servers = $null}
$serverids = [System.Collections.ArrayList]::new()
for($j = 0; $j -lt $myArray.Count; $j++){
if($myArray[$j].userid -eq $uniqueUsers[$i]){
$serverids.Add($myArray[$j].serverid)
}
}
$singleMapping.servers = $serverids
$allMappings.Add($singleMapping)
}
$mosscontent = $allMappings | ConvertTo-Json
$mosscontent
}
catch {
$date+" - Error in creating content block for MOSS: "+$_ | Out-File -Append $errorlog_file_path
exit
}
# Create complete array including contentblock for MOSS API call
try {
$moss = #"
{
"type": "server.usage",
"data": {
"users": $mosscontent
}
}
"#
}
catch {
$date+" - Error in creating array for POST to MOSS: "+$_ | Out-File -Append $errorlog_file_path
exit
}
# 5. Call MOSS prod, dev and qa with the whole list of servers and users
# Authenticating to MOSS
$query_file_path_dev_pw = "~\Documents\MOSSDevEncryptedPassword_"
$query_file_path_qa_pw = "~\Documents\MOSSQaEncryptedPassword_"
$query_file_path_prod_pw = "~\Documents\MOSSProdEncryptedPassword_"
# Function to store credentials
function get_encrypted_content {
param (
[String] $file_path,
[String] $password
)
# Check if credentials file exis
if ( -Not (Test-Path -Path $file_path)) {
switch ($password) {
dev {
# Get credentials
Read-Host -Prompt "Enter password for mos-windows-us-dev-client-id" -AsSecureString | ConvertFrom-SecureString | Out-File -FilePath $file_path
}
qa {
# Get credentials
Read-Host -Prompt "Enter password for mos-windows-us-qa-client-id" -AsSecureString | ConvertFrom-SecureString | Out-File -FilePath $file_path
}
prod {
# Get credentials
Read-Host -Prompt "Enter password for mos-windows-us-prod-client-id" -AsSecureString | ConvertFrom-SecureString | Out-File -FilePath $file_path
}
}
}
# Read credentials from file
$Encrypted_value = Get-Content -Path $file_path
# Decrypt credentials from file
return [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR((ConvertTo-SecureString $Encrypted_value)))
}
# Define username and password
$clientid_dev = "mos-windows-us-dev"
$clientid_qa = "mos-windows-us-qa"
$clientid_prod = "mos-windows-us-prod"
$dev_pass = get_encrypted_content $query_file_path_dev_pw "dev"
$qa_pass = get_encrypted_content $query_file_path_qa_pw "qa"
$prod_pass = get_encrypted_content $query_file_path_prod_pw "prod"
[System.Security.SecureString]$clientsecret_dev = ConvertTo-SecureString -String $dev_pass -AsPlainText -Force
[System.Security.SecureString]$clientsecret_qa = ConvertTo-SecureString -String $qa_pass -AsPlainText -Force
[System.Security.SecureString]$clientsecret_prod = ConvertTo-SecureString -String $prod_pass -AsPlainText -Force
#Prepare static variables
$MOSSToken_dev = 'https://auth.00.idp.eu01.stackit.cloud/oauth/token'
$MOSSToken_qa = 'https://auth.01.idp.eu01.stackit.cloud/oauth/token'
$MOSSToken_prod = 'https://auth.01.idp.eu01.stackit.cloud/oauth/token'
$MOSSUrl_dev = "https://stackit-service-mos-dev.apps.01.cf.eu01.stackit.cloud/v1/events"
$MOSSUrl_qa = "https://stackit-service-mos-qa.apps.01.cf.eu01.stackit.cloud/v1/events"
$MOSSUrl_prod = "https://stackit-service-mos.apps.01.cf.eu01.stackit.cloud/v1/events"
$body = #{grant_type='client_credentials'}
#Set function to get all customerinfo from all portals
function call_moss {
param (
[String] $clientid,
[SecureString] $clientsecret,
[String] $MOSSToken,
[String] $MOSSUrl
)
$cred = New-Object -typename System.Management.Automation.PSCredential -ArgumentList $clientid, $clientsecret
#Get Token from MOSS
$Response = Invoke-RestMethod -Uri $MOSSToken -Method Post -Credential $cred -Body $body -ContentType "application/x-www-form-urlencoded"
$Token = $Response.access_token
$Tokenfinal = "Bearer " + $Token
#Post Content to MOSS
Invoke-RestMethod -Uri $MOSSUrl -Method Post -Headers #{'Authorization' = $Tokenfinal } -Body $moss -ContentType "application/json"
}
#Call function to Call MOSS
try
{
Write-Host "Call to MOSS Dev.."
call_moss $clientid_dev $clientsecret_dev $MOSSToken_dev $MOSSUrl_dev
Write-Host "Call to MOSS QA.."
call_moss $clientid_qa $clientsecret_qa $MOSSToken_qa $MOSSUrl_qa
Write-Host "Call to MOSS Prod"
call_moss $clientid_prod $clientsecret_prod $MOSSToken_prod $MOSSUrl_prod
}
catch
{
$date+" - Error in calling MOSS: "+$_ | Out-File -Append $errorlog_file_path
exit
}
# 6. Create daily logs with users reported to MOSS
$date = $(get-date).tostring()
$log = foreach ($item in $filteredsi)
{
$item
}
echo $date $log | Out-file -Append $log_file_path
# delete logs older than 60 days
$limit = (Get-Date).AddDays(-60)
$path = "C:\Program Files\test\logs"
# Delete files older than the $limit.
Get-ChildItem -Path $path -Recurse -Force | Where-Object { !$_.PSIsContainer -and $_.CreationTime -lt $limit } | Remove-Item -Force
# Create activity file to check if script is working
$temp_file_path = "C:\Program Files\test\tempfile*"
if (Test-Path $temp_file_path)
{
Remove-Item $temp_file_path
}
[string]$filePath = "C:\Program Files\test\tempfile";
[string]$directory = [System.IO.Path]::GetDirectoryName($filePath);
[string]$strippedFileName = [System.IO.Path]::GetFileNameWithoutExtension($filePath);
[string]$extension = [System.IO.Path]::GetExtension($filePath);
[string]$newFileName = $strippedFileName + "_" + (Get-Date).ToString('MM-dd-yyyy') + $extension;
[string]$newFilePath = [System.IO.Path]::Combine($directory, $newFileName);
New-Item $newFilePath
Additional scripts that are being called:
-> request_database.ps1
# 1. Get path for log files
param(
[string]$log_file_path = "C:\Program Files\test\logs\request_database.log",
[string]$errorlog_file_path = "C:\Program Files\test\logs\request_database_errors.log"
)
# 2. Get credentials to access Octopus DB
try {
# Define username and password security string files
$mysql_user_file_path = "~\Documents\ue_"
$mysql_pass_file_path = "~\Documents\pe_"
# Functions
function get_encrypted_content {
param (
[String] $file_path,
[String] $user_or_pass
)
# Check if credentials file exist
if ( -Not (Test-Path -Path $file_path)) {
switch ($user_or_pass) {
msqu {
# Get credentials
Read-Host -Prompt "Please, enter MySQL username" -AsSecureString | ConvertFrom-SecureString | Out-File -FilePath $file_path
}
msqp {
# Get credentials
Read-Host -Prompt "Please, enter MySQL password" -AsSecureString | ConvertFrom-SecureString | Out-File -FilePath $file_path
}
}
}
# Read credentials from file
$Encrypted_value = Get-Content -Path $file_path
# Decrypt credentials from file
return [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR((ConvertTo-SecureString $Encrypted_value)))
}
# Define username and password
$my_sql_user = get_encrypted_content $mysql_user_file_path "msqu"
$my_sql_pass = get_encrypted_content $mysql_pass_file_path "msqp"
[System.Security.SecureString]$SecPwd = ConvertTo-SecureString -String $my_sql_pass -AsPlainText -Force
$Credential = new-object -typename System.Management.Automation.PSCredential -argumentlist #($my_sql_user,$SecPwd)
}
catch {
$(get-date).tostring() +" - Error in fetching SQL user/pwd: "+$_ | Out-File -Append $errorlog_file_path
exit
}
$error_string = ""
# 3. Execute DB query
# If you want the script to continue on error you have to provide $true as second parameter
# Example: execute_db_query $exclude_stackitadmin_users_query $true
function execute_db_query {
param (
[String] $query,
[bool] $continueOnError = $false
)
try {
# Query Octopus DB
Connect-MySqlServer -Credential $Credential -Server localhost
if ($continueOnError) {
$query_results = Invoke-MySqlQuery -Query $query
} else {
$query_results = Invoke-MySqlQuery -Query $query -ErrorAction Stop
}
Disconnect-MySqlServer
return $query_results
}
catch {
$error_message = $(get-date).tostring() + " - Error in DB Query 1: " + $_
$error_message | Out-File -Append $errorlog_file_path
Write-Error $error_message
exit
}
}
-> exclude_users.ps1 (probably not needed for the task but the overall script doesn't work without it)
# 1. Get path for log files or use default
param(
[string]$log_file_path = "C:\Program Files\test\logs\exclude_users.log",
[string]$errorlog_file_path = "C:\Program Files\test\logs\errors\exclude_users_errors.log"
)
Import-Module -Name "C:\Program Files\test\request_database.ps1" -ArgumentList $log_file_path, $errorlog_file_path -Verbose
# Exclude StackitAdmin users
$exclude_stackitadmin_users_query =
#"
update oc_reporter.ws_user as updated_user,
(
select id from oc_reporter.ws_user
where username = "StackITAdmin" and exclude_spla = "no"
) as us
set
exclude_spla = "yes",
exclude_spla_reason = "nh"
where updated_user.id = us.id;
"#
execute_db_query($exclude_stackitadmin_users_query)
# Exclude users on dev & qa environment
$exclude_users_on_dev_qa_query =
#"
update oc_reporter.ws_installed_software as updated_software,
(
select ws.access_device_ids, min(ws.access_device_labels), min(ws.user_label), excluded_users, min(ws.id) as id from oc_reporter.ws_user
join oc_reporter.ws_installed_software as ws
on ws.user_id = ws_user.id
join oc_reporter.ws_customer as wc
on wc.id = ws_user.customer_id
left join
(select access_device_ids, count(1) as excluded_users from oc_reporter.ws_user as wu
join oc_reporter.ws_customer as wc
on wc.id = wu.customer_id
join oc_reporter.ws_installed_software as ws
on ws.user_id = wu.id
where
(internal_id like "d-%" or internal_id like "q-%") and
locate(',', access_device_ids) = 0 and
ws.exclude_spla = "yes" and
ws.label = "Microsoft Remote Desktop Services" and
wu.username != "StackitAdmin"
group by access_device_ids, wu.exclude_spla) as servers
on servers.access_device_ids = ws.access_device_ids
where
ws.exclude_spla = "no" and
ws.label = "Microsoft Remote Desktop Services" and
(internal_id like "d-%" or internal_id like "q-%") and
locate(',', ws.access_device_ids) = 0
group by ws.access_device_ids
having (excluded_users = 1 or excluded_users is null)
) as us
set
exclude_spla = "yes",
exclude_spla_reason = "admin"
where updated_software.id = us.id;
"#
# run twice to exlude 2 users per vm
execute_db_query($exclude_users_on_dev_qa_query)
execute_db_query($exclude_users_on_dev_qa_query)
# Exclude users from our mos-windows-2 project
$exclude_users_from_our_projects =
#"
update oc_reporter.ws_installed_software as ins,
(
select ws.access_device_ids, min(ws.access_device_labels), min(ws.user_label), excluded_users, min(ws.id) as id, min(wu.id) from oc_reporter.ws_user as wu
join oc_reporter.ws_installed_software as ws
on ws.user_id = wu.id
join oc_reporter.ws_device as wd
on wd.id = ws.access_device_ids
left join (
select ws.access_device_ids, min(ws.id), min(wu.id), count(1) as excluded_users from oc_reporter.ws_user as wu
join oc_reporter.ws_installed_software as ws
on ws.user_id = wu.id
join oc_reporter.ws_device as wd
on wd.id = ws.access_device_ids
where
ws.exclude_spla = "yes" and
ws.label = "Microsoft Remote Desktop Services" and
LOCATE(',',access_device_ids) = 0 and
(
hkey like "%d57abb0200304506879bd8037f7a49cb%" or
hkey like "%fce60e2c938c49e4a37687492a45b652%" or
hkey like "%8eb91d45f25b45978b71abb0e06a0443%" or
hkey like "%66ad75e4ff624f7e940dc363549c8404%" or
hkey like "%351aa84fb9b54be896112b36ae15dd48%" or
hkey like "%64edd6c19e17417d86094e6a02610eed%"
) and
wu.username != "StackitAdmin"
group by ws.access_device_ids
) as excluded_ws
on
excluded_ws.access_device_ids = ws.access_device_ids
where
ws.exclude_spla = "no" and
ws.label = "Microsoft Remote Desktop Services" and
LOCATE(',',ws.access_device_ids) = 0 and
(
hkey like "%d57abb0200304506879bd8037f7a49cb%" or
hkey like "%fce60e2c938c49e4a37687492a45b652%" or
hkey like "%8eb91d45f25b45978b71abb0e06a0443%" or
hkey like "%66ad75e4ff624f7e940dc363549c8404%" or
hkey like "%351aa84fb9b54be896112b36ae15dd48%" or
hkey like "%64edd6c19e17417d86094e6a02610eed%"
) and
wu.domain not like "%HOP01%" and
wu.domain not like "%WSUS01%" and
wu.domain not like "%OCKMS%" and
wu.domain not like "%AZDVOP%"
group by ws.access_device_ids
having (excluded_users = 1 or excluded_users is null)
) as rds
set
exclude_spla = "yes",
exclude_spla_reason = "admin"
where ins.id = rds.id;
"#
# run twice to exlude 2 users per vm
execute_db_query($exclude_users_from_our_projects)
execute_db_query($exclude_users_from_our_projects)

The order was wrong, as well as lots of unneccessary elements causing errors and crashes.
Current working code is:
# Compare the data between the MOSS and the Octopus
# Define log file path
$date = $(get-date).tostring()
$currentdate = get-date -format yyyy-MM-dd
$log_file_path = "[FILE PATH]"
$errorlog_file_path = "[FILE PATH]"
# 1. Call MOSS (dev, qa, prod) to get the data for all servers created in the last 48 hours
# Authenticating to MOSS
$query_file_path_dev_pw = "[FILE PATH]"
$query_file_path_qa_pw = "[FILE PATH]"
$query_file_path_prod_pw = "[FILE PATH]"
# Function to store credentials
function get_encrypted_content {
param (
[String] $file_path,
[String] $password
)
# Check if credentials file exis
if ( -Not (Test-Path -Path $file_path)) {
switch ($password) {
dev {
# Get credentials
Read-Host -Prompt "Enter password for mos-windows-us-dev-client-id" -AsSecureString | ConvertFrom-SecureString | Out-File -FilePath $file_path
}
qa {
# Get credentials
Read-Host -Prompt "Enter password for mos-windows-us-qa-client-id" -AsSecureString | ConvertFrom-SecureString | Out-File -FilePath $file_path
}
prod {
# Get credentials
Read-Host -Prompt "Enter password for mos-windows-us-prod-client-id" -AsSecureString | ConvertFrom-SecureString | Out-File -FilePath $file_path
}
}
}
# Read credentials from file
$Encrypted_value = Get-Content -Path $file_path
# Decrypt credentials from file
return [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR((ConvertTo-SecureString $Encrypted_value)))
}
# Define username and password
$clientid_dev = "[USERNAME]"
$clientid_qa = "[USERNAME]"
$clientid_prod = "[USERNAME]"
$dev_pass = get_encrypted_content $query_file_path_dev_pw "dev"
$qa_pass = get_encrypted_content $query_file_path_qa_pw "qa"
$prod_pass = get_encrypted_content $query_file_path_prod_pw "prod"
[System.Security.SecureString]$clientsecret_dev = ConvertTo-SecureString -String $dev_pass -AsPlainText -Force
[System.Security.SecureString]$clientsecret_qa = ConvertTo-SecureString -String $qa_pass -AsPlainText -Force
[System.Security.SecureString]$clientsecret_prod = ConvertTo-SecureString -String $prod_pass -AsPlainText -Force
# Time variable
$date48h = ("{0:yyyy-MM-ddThh:mm:ss}" -f ((get-date).Addhours(-48))).split("T").split(":")
$date = $date48h[0]
$hour = $date48h[1]
$min = $date48h[2]
$sec = $date48h[3]
#Prepare static variables
$MOSSToken_dev = '[URL]'
$MOSSToken_qa = '[URL]'
$MOSSToken_prod = '[URL]'
$MOSSUrl_dev = "[URL]"
$MOSSUrl_qa = "[URL]"
$MOSSUrl_prod = "[URL]"
$body = #{grant_type='client_credentials'}
#Set function to get all customerinfo from all portals
function call_moss {
param (
[String] $clientid,
[SecureString] $clientsecret,
[String] $MOSSToken,
[String] $MOSSUrl
)
$cred = New-Object -typename System.Management.Automation.PSCredential -ArgumentList $clientid, $clientsecret
#Get Token from MOSS
$Response = Invoke-RestMethod -Uri $MOSSToken -Method Post -Credential $cred -Body $body -ContentType "application/x-www-form-urlencoded"
$Token = $Response.access_token
$Tokenfinal = "Bearer " + $Token
#Post Content to MOSS
Invoke-RestMethod -Uri $MOSSUrl -Method Get -Headers #{'Authorization' = $Tokenfinal } -ContentType "application/json"
}
#Call function to Call MOSS
try
{
Write-Host "Call to MOSS Dev.."
$get_moss_dev = call_moss $clientid_dev $clientsecret_dev $MOSSToken_dev $MOSSUrl_dev
Write-Host "Call to MOSS QA.."
$get_moss_qa = call_moss $clientid_qa $clientsecret_qa $MOSSToken_qa $MOSSUrl_qa
Write-Host "Call to MOSS Prod"
$get_moss_prod = call_moss $clientid_prod $clientsecret_prod $MOSSToken_prod $MOSSUrl_prod
}
catch
{
$date+" - Error in calling MOSS: "+$_ | Out-File -Append $errorlog_file_path
exit
}
$moss_dev_serverids = $get_moss_dev.items.id
$moss_qa_serverids = $get_moss_qa.items.id
$moss_prod_serverids = $get_moss_prod.items.id
$moss_serverid_arr = #($moss_dev_serverids, $moss_qa_serverids, $moss_prod_serverids)
# 2. Call Octopus to get the data for new servers created in the last 36 hours
Import-Module -Name "[FILE PATH]" -ArgumentList $log_file_path, $errorlog_file_path -Verbose
# Calculate timestamp
$DateTime = Get-Date #or any other command to get DateTime object
$CurrentUnixTime = ([DateTimeOffset]$DateTime).ToUnixTimeSeconds()
$queryTime = $CurrentUnixTime - (36 * 3600)
$get_new_servers_query_oc =
#"
select id, lower(SUBSTRING_INDEX(SUBSTRING_INDEX(ref_id, "-", -6), "-", 5)) as server_id, ref_id, label from oc_reporter.ws_device where type_id = "vm" and operating_system like "%Windows%" and created > $queryTime;
"#
$query = execute_db_query $get_new_servers_query_oc
$serverid_oc = $query.server_id
$serverid_oc_arr = #($serverid_oc)
# 3. Compare the properties in MOSS and Octopus
$unmatching_serverids = $serverid_oc_arr | Where {$moss_serverid_arr -NotContains $_}
$error_report = #($unmatching_serverids)
# Create daily logs with servers in Octopus that are unregistered in MOSS
$date = $(get-date).tostring()
$log = $error_report
echo $date $log | Out-file -Append $log_file_path
# delete logs older than 60 days
$limit = (Get-Date).AddDays(-60)
$path = "[FILE PATH]"
# Delete files older than the $limit.
Get-ChildItem -Path $path -Recurse -Force | Where-Object { !$_.PSIsContainer -and $_.CreationTime -lt $limit } | Remove-Item -Force
# 4. Generate summary with all errors and send a notification if there is an error. Schedule a task to check once per day.
If ($error_report.Count -eq 0) {
exit
}
else {
$JSONBody = [PSCustomObject][Ordered] #{
"type" = "MessageCard"
"title" = "Octopus Alerts"
"text" = "Servers located in Octopus, that are not registered in MOSS. <br>
Please check logs."
}
$TeamsMessageBody = ConvertTo-Json $JSONBody
$parameters = #{
"URI" = '[URL]'
"Method" = 'POST'
"Body" = $TeamsMessageBody
"ContentType" = 'application/json'
}
Invoke-RestMethod #parameters
}
# Create activity file to check if script is working
$temp_file_path = "[FILE PATH]"
if (Test-Path $temp_file_path)
{
Remove-Item $temp_file_path
}
[string]$filePath = "[FILE PATH]";
[string]$directory = [System.IO.Path]::GetDirectoryName($filePath);
[string]$strippedFileName = [System.IO.Path]::GetFileNameWithoutExtension($filePath);
[string]$extension = [System.IO.Path]::GetExtension($filePath);
[string]$newFileName = $strippedFileName + "_" + (Get-Date).ToString('MM-dd-yyyy') + $extension;
[string]$newFilePath = [System.IO.Path]::Combine($directory, $newFileName);
New-Item $newFilePath
Also, as already mentioned the exclude_users script was completely not needed. The only additionally included script is the request_database script.

Related

Merge multiple configuration files using powershell

My Powershell script receives multiple Microsoft Office365 DSC policy configuration policy files as input in a folder as below:
file1.ps1
Configuration EXOSharingPolicy {
param (
[parameter()]
[System.Management.Automation.PSCredential]
$GlobalAdmin
)
if ($null -eq $GlobalAdmin) {
<# Credentials #>
$GlobalAdmin = Get-Credential -Message "Credentials"
}
else {
$Credential = $GlobalAdmin
}
$OrganizationName = $Credential.UserName.Split('#')[1]
Import-DscResource -ModuleName 'Microsoft365DSC' -ModuleVersion '1.22.907.1'
Node localhost
{
EXOSharingPolicy 8b39ae5a-f4ed-4bdb-932d-fbb9397f7fc6
{
Credential = $Credential;
Default = $True;
Domains = #("Anonymous:CalendarSharingFreeBusyReviewer");
Enabled = $True;
Ensure = "Present";
Name = "Default Sharing Policy";
}
}
}
file2.ps1
Configuration AADTenantDetails {
param (
[parameter()]
[System.Management.Automation.PSCredential]
$GlobalAdmin
)
if ($null -eq $GlobalAdmin) {
<# Credentials #>
$GlobalAdmin = Get-Credential -Message "Credentials"
}
else {
$Credential = $GlobalAdmin
}
$OrganizationName = $Credential.UserName.Split('#')[1]
Import-DscResource -ModuleName 'Microsoft365DSC' -ModuleVersion '1.22.907.1'
Node localhost
{
AADTenantDetails 5cfcabd5-9c82-4bed-9934-09e1cf20c71b
{
Credential = $Credential;
IsSingleInstance = "Yes";
MarketingNotificationEmails = #();
SecurityComplianceNotificationMails = #();
SecurityComplianceNotificationPhones = #();
TechnicalNotificationMails = #("admin#tech.net.au");
}
}
}
file3.ps1
Configuration EXOEmailAddressPolicy {
param (
[parameter()]
[System.Management.Automation.PSCredential]
$GlobalAdmin
)
if ($null -eq $GlobalAdmin) {
<# Credentials #>
$GlobalAdmin = Get-Credential -Message "Credentials"
}
else {
$Credential = $GlobalAdmin
}
$OrganizationName = $Credential.UserName.Split('#')[1]
Import-DscResource -ModuleName 'Microsoft365DSC' -ModuleVersion '1.22.907.1'
Node localhost
{
EXOEmailAddressPolicy a2188f3f-80d5-419c-b229-063fc2c18dbf
{
Credential = $Credential;
EnabledEmailAddressTemplates = #("SMTP:#$OrganizationName");
EnabledPrimarySMTPAddressTemplate = "#$OrganizationName";
Ensure = "Present";
ManagedByFilter = "";
Name = "Default Policy";
Priority = "Lowest";
}
}
}
I have several of these configuration files.
Is there a way in powershell to combine/merge these files so I end up with one file with all configurations like below.
Configuration CombinedPolicy {
param (
[parameter()]
[System.Management.Automation.PSCredential]
$GlobalAdmin
)
if ($null -eq $GlobalAdmin) {
<# Credentials #>
$GlobalAdmin = Get-Credential -Message "Credentials"
}
else {
$Credential = $GlobalAdmin
}
$OrganizationName = $Credential.UserName.Split('#')[1]
Import-DscResource -ModuleName 'Microsoft365DSC' -ModuleVersion '1.22.907.1'
Node localhost
{
EXOSharingPolicy 8b39ae5a-f4ed-4bdb-932d-fbb9397f7fc6
{
Credential = $Credential;
Default = $True;
Domains = #("Anonymous:CalendarSharingFreeBusyReviewer");
Enabled = $True;
Ensure = "Present";
Name = "Default Sharing Policy";
}
AADTenantDetails 5cfcabd5-9c82-4bed-9934-09e1cf20c71b
{
Credential = $Credential;
IsSingleInstance = "Yes";
MarketingNotificationEmails = #();
SecurityComplianceNotificationMails = #();
SecurityComplianceNotificationPhones = #();
TechnicalNotificationMails = #("jarrod#j-tech.net.au");
}
EXOEmailAddressPolicy a2188f3f-80d5-419c-b229-063fc2c18dbf
{
Credential = $Credential;
EnabledEmailAddressTemplates = #("SMTP:#$OrganizationName");
EnabledPrimarySMTPAddressTemplate = "#$OrganizationName";
Ensure = "Present";
ManagedByFilter = "";
Name = "Default Policy";
Priority = "Lowest";
}
}
}
So, in the combined configuration file I only need the section under Node localhost from each of the individual configuration files to be combined instead of entire file contents merged.
I need this so I can apply all the DSC configurations an office 365 tenancy at once instead of applying individual configurations.
Hope that makes sense.
The following script gets the desired combined file. You can only have the DSC files in the folder "PathToDSCFiles". This also only works if the given DSC files all have a format like given in the examples, because it just skips the 21 lines in the given scripts and continues from there. If you have other formats from your files, you will have to find a way to just select everything that comes after "Node Localhost".
$path = "PathToDSCFiles"
$PathToCombinedFile = "PathToCombinedFile"
$list = (Get-ChildItem -Path $path).Name
$first = $list | select -First 1
$SecondWord = (Get-Content $path\$first).Split(" ")[1]
((Get-Content $path\$first)[0]) -creplace($SecondWord,"CombinedPolicy") > $PathToCombinedFile
Get-Content $path\$first | select -skip 1 | select -skipLast 2 >> $PathToCombinedFile
foreach($file in ($list | select -skip 1)){
if ($file -eq ($list | select -skip 1)[-1]){
Get-Content -Path $path\$file | select -Skip 21 >> $PathToCombinedFile
}else{
Get-Content -Path $path\$file | select -Skip 21 | select -SkipLast 2 >> $PathToCombinedFile
}
}

Function to check DB in remote server - Timeout question

I am writing a function in PowerShell that will do the following when given some parameter:
Get Credetials to connect to remote SQL Server
Open SQL Connection with given parameters. If Opening connection takes longer than value in $sqlconnectionTimeout, it will report a log file with Timeout error
A query will be introduce in a specific DB from Remote Server. If executing command takes longer than value in $sqlCommandTimeout, it will report a log file with error.
The function will create a html file with connectivity result (True or False). If True, the script will check log reports within last 5 minutes. Connectivity will be True if no logs are found. If False, a log will be created and saved in a specific folder.
About the Timeout variables, No matter what I put in the variable, The Connection is opened and the command is executed. Even If I put 0 forcing it to output a timeout error. The connection is opened and the query executed.
My question is: What am I missing here so that Timeout takes effect when executing the function?
Thanks for your help
This is the Code:
# Editing Variables
$user = "userName"
$DatabaseName = "SampleDB"
$query = "INSERT INTO [SampleDB].[dbo].[HealthChecks]([DateTime],[Source],[User]) VALUES (CURRENT_TIMESTAMP, ##SERVERNAME, CURRENT_USER)"
#Set timeout when stablishing connection (Default = 15)
$sqlConnectionTimeout = 3
#Set timeout when executing sql command (Default = 30)
$sqlCommandTimeout = 3
# 1. R-etrieve the data
function Test-SqlConnection
{
param(
[Parameter(Mandatory)]
[string]$serverURL,
[Parameter(Mandatory)]
[string]$DatabaseName,
[Parameter(Mandatory)]
[string]$user,
[Parameter(Mandatory)]
[string]$query,
[Parameter(Mandatory=$False)]
[string]$sqlConnectionTimeout,
[Parameter(Mandatory=$False)]
[string]$sqlCommandTimeout
)
try
{
$logTime = (Get-Date -Format "hh-mm_dd-MM-yyyy")
$reportPath = "C:\Logs\DBLogs\"
$DBCheckPath = "C:\inetpub\wwwroot\UptimeRobot\"
# Obtain Credentials without revealing password in script
$userName = $user
$passwordFile = "C:\Utilities\keys\password.txt"
$keyFile = "C:\Utilities\keys\aes.key"
$key = Get-Content $keyFile
$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $userName, (Get-Content $passwordFile | ConvertTo-SecureString -Key $key)
# Open Connection to DB
$userName = $Credential.UserName
$password = $Credential.GetNetworkCredential().Password
$connectionString = "Data Source={0};database={1};User ID={2};Password={3};Connection Timeout={4}" -f $serverURL,$DatabaseName,$userName,$password,$sqlConnectionTimeout
$sqlConnection = New-Object System.Data.SqlClient.SqlConnection $ConnectionString
$sqlConnection.Open()
#3. P-repare the UpdateRequest
$sqlCommand = New-Object System.Data.SQLClient.SQLCommand
$sqlCommand.CommandTimeout = $sqlCommandTimeout
$sqlCommand.Connection = $sqlConnection
$sqlCommand.CommandText = $query
$res = $sqlCommand.ExecuteNonQuery()
# 4. S-end reports to log file in JSon format
$sqlLogs = Get-Childitem $reportPath -filter *.json | Where-Object {$_.LastWriteTime -gt [datetime]::Now.AddMinutes(-5)}
if($res -eq 1)
{
if($sqlLogs.count -gt 0)
{
$connectivityResult = #{
DBServer = $ServerFriendlyName
DataCenter = $dataCenter
Connectivity = $False
}
}else
{
$connectivityResult =#{
DBServer = $ServerFriendlyName
DataCenter = $dataCenter
Connectivity = $True
}
}
$connectivityResult | ConvertTo-Json -Compress | Out-File -Encoding utf8 -LiteralPath ($DBCheckPath + "DBCheck.html")
}
##TODO: Only report Connectivity Serviceable= $false when there are two logs with $false value within 5 minutes.
}catch
{
$errorMessage = $_.Exception
## Only return $false if the exception was thrown because it can't connect for some reason. Otherwise
## throw the general exception
if ($errorMessage -match 'The server was not found' -or 'timeout')
{
$connectivityResult = #{
DBServer = $ServerFriendlyName
DataCenter = $dataCenter
Connectivity = $False
ErrorMessage = $errorMessage.GetBaseException().message
}
$connectivityResult | ConvertTo-Json | Out-File -Encoding utf8 -LiteralPath ($reportPath + $logTime + ".json")
$connectivityResult | ConvertTo-Json | Out-File -Encoding utf8 -LiteralPath ($DBCheckPath + "DBCheck.html")
}
}
finally
{
$sqlConnection.Close()
}
}

When Authenticating in Microsoft Graph and get querying, it outputs#odata.context and #odata.nextLink

So here is my code to get the AuthToken for my Tenant ID, this is from Microsoft and generates a JWT to use as authorization in the HTTP header:
function Get-AuthToken {
[cmdletbinding()]
param
(
[Parameter(Mandatory=$true)]
$User
)
$userUpn = New-Object "System.Net.Mail.MailAddress" -ArgumentList $User
$tenant = $userUpn.Host
Write-Host "Checking for AzureAD module..."
$AadModule = Get-Module -Name "AzureAD" -ListAvailable
if ($AadModule -eq $null) {
Write-Host "AzureAD PowerShell module not found, looking for AzureADPreview"
$AadModule = Get-Module -Name "AzureADPreview" -ListAvailable
}
if ($AadModule -eq $null) {
write-host
write-host "AzureAD Powershell module not installed..." -f Red
write-host "Install by running 'Install-Module AzureAD' or 'Install-Module AzureADPreview' from an elevated PowerShell prompt" -f Yellow
write-host "Script can't continue..." -f Red
write-host
exit
}
if($AadModule.count -gt 1){
$Latest_Version = ($AadModule | select version | Sort-Object)[-1]
$aadModule = $AadModule | ? { $_.version -eq $Latest_Version.version }
# Checking if there are multiple versions of the same module found
if($AadModule.count -gt 1){
$aadModule = $AadModule | select -Unique
}
$adal = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.dll"
$adalforms = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll"
}else {
$adal = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.dll"
$adalforms = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll"
}
[System.Reflection.Assembly]::LoadFrom($adal) | Out-Null
[System.Reflection.Assembly]::LoadFrom($adalforms) | Out-Null
$clientId = "d1ddf0e4-d672-4dae-b554-9d5bdfd93547"
$redirectUri = "urn:ietf:wg:oauth:2.0:oob"
$resourceAppIdURI = "https://graph.microsoft.com"
$authority = "https://login.microsoftonline.com/$Tenant"
try {
$authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority
$platformParameters = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.PlatformParameters" -ArgumentList "Auto"
$userId = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.UserIdentifier" -ArgumentList ($User, "OptionalDisplayableId")
$authResult = $authContext.AcquireTokenAsync($resourceAppIdURI,$clientId,$redirectUri,$platformParameters,$userId).Result
# If the accesstoken is valid then create the authentication header
if($authResult.AccessToken){
# Creating header for Authorization token, we dont necessarily need it, just the JWT auth token.
$authHeader = #{
'Content-Type'='application/json'
'Authorization'="Bearer " + $authResult.AccessToken
'ExpiresOn'=$authResult.ExpiresOn
}
# Write-Output
return $authResult
# return $authHeader
}
}catch {
write-host $_.Exception.Message -f Red
break
}
}
So basically at the bottom I use this JWT auth token from the function above and place it as an Authorization field in the HTTP header and it should return JSON from the Graph API:
$authData = Get-AuthToken -User acct#pennitout.com
$accessJWToken = $authData.AccessToken
$apiUrl = "https://graph.microsoft.com/v1.0/users?$select=displayName"
Invoke-RestMethod -Headers #{"Authorization" = "Bearer $accessJWToken"} -Uri $apiUrl -Method Get -ContentType "application/json"
And the above code beautifully absolutely uselessly output it returns instead is:
#odata.context #odata.nextLink
-------------- ---------------
https://graph.microsoft.com/v1.0/$metadata#users https://graph.microsoft.com/v1.0/users?=displayName&$skiptoken=RFNwdAIAAQAAABg6YWdyYW50QHRlcnJhbmV1dHJhbC5jb20pVXNlcl85MzA2OWJlYy0zZjFjLTRiNDQtOTZjMS
Please Help with this thanks I really appreciate
Can you please check there might be issue that Token has expired, Please try to generate new token and check it for the Odata next link which might have caused the error.
Here is the document for reference:Token Duration

SharePoint Online Powershell Folder creation

I need to be able to script folder creation from a csv into a SharePoint Online document library with each folder with permission inheritance disabled and for different user to each folder to be added.
The following code can create the folders and disable the inheritance but it seems to try add a group but not a user. How to make it add a user instead?
Thanks.
### Get the user credentials
$credential = Get-Credential
$username = $credential.UserName
$password = $credential.GetNetworkCredential().Password
$securePassword = ConvertTo-SecureString $password -AsPlainText -Force
### Input Parameters
$url = 'URL HERE'
$csvfilepath='C:\Scripts\data.csv'
$libname ='BUS61'
### References
# Specified the paths where the dll's are located.
Add-Type -Path 'C:\Scripts\SPOCmdlets\Microsoft.SharePoint.Client.dll'
Add-Type -Path 'C:\Scripts\SPOCmdlets\Microsoft.SharePoint.Client.Runtime.dll'
### CreateFolder with Permissions Function
function CreateFolderWithPermissions()
{
# Connect to SharePoint Online and get ClientContext object.
$clientContext = New-Object Microsoft.SharePoint.Client.ClientContext($url)
$credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($username, $securePassword)
$clientContext.Credentials = $credentials
Function GetRole
{
[CmdletBinding()]
param
(
[Parameter(Mandatory = $true, Position = 1)]
[Microsoft.SharePoint.Client.RoleType]$rType
)
$web = $clientContext.Web
if ($web -ne $null)
{
$roleDefs = $web.RoleDefinitions
$clientContext.Load($roleDefs)
$clientContext.ExecuteQuery()
$roleDef = $roleDefs | Where-Object { $_.RoleTypeKind -eq $rType }
return $roleDef
}
return $null
}
# Get the SharePoint web
$web = $clientContext.Web;
$clientContext.Load($web)
#Get the groups
$groups = $web.SiteGroups
$clientContext.Load($groups)
$clientContext.ExecuteQuery()
#Read CSV File and iterate
$csv = Import-CSV $csvfilepath
foreach ($row in $csv)
{
#Create Folder
$folder = $web.Folders.Add($libname + "/" + $row.Folder)
$clientContext.Load($folder)
$clientContext.ExecuteQuery()
#Assign Role
$group = $groups.GetByName($row.Group)
$clientContext.Load($group)
$clientContext.ExecuteQuery()
$roleType= $row.Role
$roleTypeObject = [Microsoft.SharePoint.Client.RoleType]$roleType
$roleObj = GetRole $roleTypeObject
$usrRDBC = $null
$usrRDBC = New-Object Microsoft.SharePoint.Client.RoleDefinitionBindingCollection($clientContext)
$usrRDBC.Add($roleObj)
# Remove inherited permissions
$folder.ListItemAllFields.BreakRoleInheritance($false, $true)
$clientContext.Load($folder.ListItemAllFields.RoleAssignments.Add($group, $usrRDBC))
$folder.Update()
$clientContext.ExecuteQuery()
# Display the folder name and permission
Write-Host -ForegroundColor Blue 'Folder Name: ' $folder.Name ' Group: '$row.Group ' Role: ' $roleType;
}
}
#Execute the function
CreateFolderWithPermissions
Let's assume that you will define user login in your CSv file. Than you have to change the line:
$group = $groups.GetByName($row.Group)
to
$user = $web.EnsureUser($row.User)
and replace all references to $group variable with $user
More generic approach for searching for a user (with for example display name) would be using Utility.ResolvePrincipal method:
[Microsoft.SharePoint.Client.Utilities.Utility]::ResolvePrincipal($clientContext, $web, "DisplayName", ([Microsoft.SharePoint.Client.Utilities.PrincipalType]::User), ([Microsoft.SharePoint.Client.Utilities.PrincipalSource]::All), $null, $false)

Opening a SSRS project using Powershell

I have a report that is copied to a number of different servers. It is imported manually and the data source properties are altered to match the current server's specs. I would like to be able to automate the process by enabling users to open a the SSRS report and dynamically alter it's shared data source properties through PowerShell. I hope you could help. You may see reference below.
The script would accept an input parameter for servername, username and password. Also, the save my password must be ticked.
I couldn't believe I managed to create a script for this. You may make use of the script below as future reference. Comments are available for each part and anything that needs to be altered has a "here" keyword , ex. Your_database_name_here .
Import-Module SqlPs
#Input parameter to get Server\Instancename of your Datasource
$Servername = Read-Host "Please enter your Servername"
$Instancename = Read-Host "Please enter your Instancename. For default instance please press enter"
Write-host ""
if ($Instancename -eq ""){
$ServerInstance = $Servername
}
Else {
$ServerInstance = $Servername +"\"+ $InstanceName
}
#Setting up SSRS Target URL. This is the location where your reports would be deployed.
if ($Instancename -eq ""){
$ReportServerUri = "http://$Servername/ReportServer//ReportService2010.asmx?wsdl"
$TargetURL = "http://$Servername/Reports"
}
Else {
$ReportServerUri = "http://$Servername/ReportServer_$Instancename//ReportService2010.asmx?wsdl"
$TargetURL = "http://$Servername/Reports_$Instancename"
}
$global:proxy = New-WebServiceProxy -Uri $ReportServerUri -UseDefaultCreden
#We would make use of SQL Server Authentication for the reports shared datasource so you need to supply a username and password.
Write-Host " SQL Server Authentication:"
$Username = Read-Host " Username"
$Password = Read-Host -AsSecureString "Password"
$type = $Proxy.GetType().Namespace
$datatype = ($type + '.Property')
$property =New-Object ($datatype);
$property.Name = “NewFolder”
$property.Value = “NewFolder”
$numproperties = 1
$properties = New-Object ($datatype + '[]')$numproperties
$properties[0] = $property;
$newFolder = $proxy.CreateFolder("Reports”, “/”, $properties);
$newFolder = $proxy.CreateFolder("Data Sources”, “/”, $properties);
$Children =$proxy.ListChildren("/",$false)
$DBname = 'Your_Database_Name_Here'
# Creating Datasource through powershell
Write-Host " Creating Datasource ..."
$Name = "Name_Your_Datasource_here"
$Parent = "/Data Sources"
$ConnectString = "data source=$Servername\$Instancename;initial catalog=$DBname"
$type = $Proxy.GetType().Namespace
$DSDdatatype = ($type + '.DataSourceDefinition')
$DSD = new-object ($DSDdatatype)
if($DSD -eq $null){
Write-Error Failed to create data source definition object
}
$CredentialDataType = ($type + '.CredentialRetrievalEnum')
$Cred = new-object ($CredentialDataType)
$CredEnum = ($CredentialDataType).Integrated
$Cred.value__=1
$DSD.CredentialRetrieval =$Cred
$DSD.ConnectString = $ConnectString
$DSD.Enabled = $true
$DSD.EnabledSpecified = $false
$DSD.Extension = "SQL"
$DSD.ImpersonateUserSpecified = $false
$DSD.Prompt = $null
$DSD.WindowsCredentials = $false
$DSD.UserName = $Username
$DSD.Password = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password))
$newDSD = $proxy.CreateDataSource($Name,$Parent,$true,$DSD,$null)
#Deploying RLD files to Target URL
Write-Host " Deploying RDL files ..."
$stream = Get-Content 'D:\Your_RDL_path_here.rdl' -Encoding byte
$warnings =#();
$proxy.CreateCatalogItem("Report","Report_Name_here","/Reports",$true,$stream,$null,[ref]$warnings)
#Let's make use of the datasource we just created for your RDL files.
$Items = $global:proxy.listchildren("/Data Sources", $true)
foreach ($item in $items)
{
$DatasourceName = $item.Name
$DatasourcePath = $item.Path
}
$RDLS = $global:proxy.listchildren("/Reports", $true)
foreach ($rdl in $rdls)
{
$report = $rdl.path
$rep = $global:proxy.GetItemDataSources($report)
$rep | ForEach-Object {
$proxyNamespace = $_.GetType().Namespace
$constDatasource = New-Object ("$proxyNamespace.DataSource")
$constDatasource.Name = $DataSourceName
$constDatasource.Item = New-Object ("$proxyNamespace.DataSourceReference")
$constDatasource.Item.Reference = $DataSourcePath
$_.item = $constDatasource.Item
$global:proxy.SetItemDataSources($report, $_)
Write-Host "Changing datasource `"$($_.Name)`" to $($_.Item.Reference)"
}
}
#Open a IE browser to view the report.
$IE=new-object -com internetexplorer.application
$IE.navigate2($TargetURL)
$IE.visible=$true
Write-Host ""
Write-Host "You may now view the Reports through the open IE browser."
Write-Host -ForegroundColor Green "**STEP COMPLETED!"