Merge multiple configuration files using powershell - 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
}
}

Related

Comparing Server IDs in two tools via PowerShell script

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.

powershell oAuth problem on ExchangeOnline

i have a script that works fine for exchange on-prem and online with basic auth.
The problem is that my organization now accetps only oAuth metod for echangeonline.
This script is for insert a quickaction in the user logging with in his mailbox.
How i have to change the connect method to work with oauth within exchangeonline?
i'll have the same commandelets after that?
Begin
{
Import-Module Activedirectory
}
Process
{
$curUser = $env:USERNAME
$curDomain = $env:USERDNSDOMAIN
$aUser = Get-ADUser -Identity "${curUser}" -Server "${curDomain}" -Properties "EmailAddress"
if ([string]::IsNullOrEmpty($aUser.EmailAddress)) {
throw "User ${curUser} has no Emailaddress"
}
$aUser.EmailAddress
"$($aUser.GivenName) $($aUser.Surname)"
}
}
function Connect-Exchange{
param(
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName
)
try {
Connect-Exchange365 -MailboxName $MailboxName
} catch {
Connect-ExchangeONPREM -MailboxName $MailboxName
}
}
function Connect-Exchange365
{
param(
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName
)
Begin
{
############## NEW CODE HERE WITH OAUTH ################
$service = "?????????????????"
if (!$service.URL) {
throw "Error connecting to EWS"
}
else
{
return $service
}
}
}
function Connect-ExchangeONPREM
{
param(
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName
)
Begin
{
## Load Managed API dll
###CHECK FOR EWS MANAGED API, IF PRESENT IMPORT THE HIGHEST VERSION EWS DLL, ELSE EXIT
$EWSDLL = (($(Get-ItemProperty -ErrorAction SilentlyContinue -Path Registry::$(Get-ChildItem -ErrorAction SilentlyContinue -Path 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Exchange\Web Services'|Sort-Object Name -Descending| Select-Object -First 1 -ExpandProperty Name)).'Install Directory') + "Microsoft.Exchange.WebServices.dll")
if (Test-Path $EWSDLL)
{
Import-Module $EWSDLL
}
else
{
"$(get-date -format yyyyMMddHHmmss):"
"This script requires the EWS Managed API 1.2 or later."
"Please download and install the current version of the EWS Managed API from"
"http://go.microsoft.com/fwlink/?LinkId=255472"
""
"Exiting Script."
exit
}
## Set Exchange Version
$ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2
## Create Exchange Service Object
$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)
## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials
#Credentials Option 1 using UPN for the windows Account
#$psCred = Get-Credential
############ $creds = New-Object System.Net.NetworkCredential($Credentials.UserName.ToString(),$Credentials.GetNetworkCredential().password.ToString())
############ $service.Credentials = $creds
#Credentials Option 2
$service.UseDefaultCredentials = $true
$service.TraceEnabled = $true
## Choose to ignore any SSL Warning issues caused by Self Signed Certificates
## Code From http://poshcode.org/624
## Create a compilation environment
$Provider=New-Object Microsoft.CSharp.CSharpCodeProvider
$Compiler=$Provider.CreateCompiler()
$Params=New-Object System.CodeDom.Compiler.CompilerParameters
$Params.GenerateExecutable=$False
$Params.GenerateInMemory=$True
$Params.IncludeDebugInformation=$False
$Params.ReferencedAssemblies.Add("System.DLL") | Out-Null
$TASource=#'
namespace Local.ToolkitExtensions.Net.CertificatePolicy{
public class TrustAll : System.Net.ICertificatePolicy {
public TrustAll() {
}
public bool CheckValidationResult(System.Net.ServicePoint sp,
System.Security.Cryptography.X509Certificates.X509Certificate cert,
System.Net.WebRequest req, int problem) {
return true;
}
}
}
'#
$TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)
$TAAssembly=$TAResults.CompiledAssembly
## We now create an instance of the TrustAll and attach it to the ServicePointManager
$TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")
[System.Net.ServicePointManager]::CertificatePolicy=$TrustAll
## end code from http://poshcode.org/624
## Set the URL of the CAS (Client Access Server) to use two options are availbe to use Autodiscover to find the CAS URL or Hardcode the CAS to use
# CAS URL Option 1 Autodiscover
$service.AutodiscoverUrl($MailboxName,{$true})
Write-host ("Using CAS Server : " + $Service.url)
## Optional section for Exchange Impersonation
#$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)
if(!$service.URL){
throw "Error connecting to EWS"
}
else
{
return $service
}
}
}
function ConvertFolderid{
param(
[Parameter(Position=0, Mandatory=$true)] [string]$hexid,
[Parameter(Position=1, Mandatory=$true)] [Microsoft.Exchange.WebServices.Data.ExchangeService]$service,
[Parameter(Position=2, Mandatory=$true)] [string]$MailboxName
)
Begin
{
$aiItem = New-Object Microsoft.Exchange.WebServices.Data.AlternateId
$aiItem.Mailbox = $MailboxName
$aiItem.UniqueId = $hexId
$aiItem.Format = [Microsoft.Exchange.WebServices.Data.IdFormat]::HexEntryId;
return $global:service.ConvertId($aiItem, [Microsoft.Exchange.WebServices.Data.IdFormat]::EWSId)
}
}
#######################
<#
.SYNOPSIS
Gets the QuickSteps folder in a Mailbox using the Exchange Web Services API
.DESCRIPTION
Gets the QuickSteps folder in a Mailbox using the Exchange Web Services API
Requires the EWS Managed API from https://www.microsoft.com/en-us/download/details.aspx?id=42951
.EXAMPLE
Example 1 To Gets the QuickSteps folder in a Mailbox using the Exchange Web Services API
Get-QuickStepsFolder -MailboxName mailbox#domain.com
#>
########################
function Get-QuickStepsFolder
{
param(
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName,
[Parameter(Position=1, Mandatory=$false)] [Microsoft.Exchange.WebServices.Data.ExchangeService]$service
)
Begin
{
#if(!$service){
$localservice = Connect-Exchange -MailboxName $MailboxName
#}
$PidTagAdditionalRenEntryIdsEx = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x36D9, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary)
$psPropset = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
$psPropset.Add($PidTagAdditionalRenEntryIdsEx)
$folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Root,$MailboxName)
try {
$IPM_ROOT = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($localservice,$folderid,$psPropset)
} catch {
$localservice = Connect-ExchangeONPREM -MailboxName $MailboxName
$IPM_ROOT = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($localservice,$folderid,$psPropset)
}
$global:service = $localservice
$binVal = $null;
$AdditionalRenEntryIdsExCol = #{}
if($IPM_ROOT.TryGetProperty($PidTagAdditionalRenEntryIdsEx,[ref]$binVal)){
$hexVal = [System.BitConverter]::ToString($binVal).Replace("-","");
##Parse Binary Value first word is Value type Second word is the Length of the Entry
$Sval = 0;
while(($Sval+8) -lt $hexVal.Length){
$PtypeVal = $hexVal.SubString($Sval,4)
$PtypeVal = $PtypeVal.SubString(2,2) + $PtypeVal.SubString(0,2)
$Sval +=12;
$PropLengthVal = $hexVal.SubString($Sval,4)
$PropLengthVal = $PropLengthVal.SubString(2,2) + $PropLengthVal.SubString(0,2)
$PropLength = [Convert]::ToInt64($PropLengthVal, 16)
$Sval +=4;
$ProdIdEntry = $hexVal.SubString($Sval,($PropLength*2))
$Sval += ($PropLength*2)
#$PtypeVal + " : " + $ProdIdEntry
$AdditionalRenEntryIdsExCol.Add($PtypeVal,$ProdIdEntry)
}
}
$QuickStepsFolder = $null
if($AdditionalRenEntryIdsExCol.ContainsKey("8007")){
$siId = ConvertFolderid -service $service -MailboxName $MailboxName -hexid $AdditionalRenEntryIdsExCol["8007"]
$QuickStepsFolderId = new-object Microsoft.Exchange.WebServices.Data.FolderId($siId.UniqueId.ToString())
$QuickStepsFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($global:service,$QuickStepsFolderId)
}
else{
Write-Host ("QuickSteps folder not found")
throw ("QuickSteps folder not found")
}
write-host "end of Get-QuickStepsFolder"
write-host " ---FLD --- $($QuickStepsFolder.DisplayName)"
return $QuickStepsFolder
}
}
function Get-ExistingStepNames{
param(
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName,
[Parameter(Position=1, Mandatory=$true)] [Microsoft.Exchange.WebServices.Data.Folder]$QuickStepsFolder
)
Begin
{
$NameList = #{}
$enc = [system.Text.Encoding]::ASCII
$PR_ROAMING_XMLSTREAM = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x7C08,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary);
$psPropset= new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
$psPropset.Add($PR_ROAMING_XMLSTREAM)
#Define ItemView to retrive just 1000 Items
$ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)
$ivItemView.Traversal = [Microsoft.Exchange.WebServices.Data.ItemTraversal]::Associated
$fiItems = $null
do{
$fiItems = $QuickStepsFolder.FindItems($ivItemView)
if($fiItems.Items.Count -gt 0){
[Void]$global:service.LoadPropertiesForItems($fiItems,$psPropset)
foreach($Item in $fiItems.Items){
$propval = $null
if($Item.TryGetProperty($PR_ROAMING_XMLSTREAM,[ref]$propval)){
[XML]$xmlVal = $enc.GetString($propval)
if(!$NameList.ContainsKey($xmlVal.CombinedAction.Name.ToLower())){
$NameList.Add($xmlVal.CombinedAction.Name.Trim().ToLower(),$xmlVal)
}
}
}
}
$ivItemView.Offset += $fiItems.Items.Count
}while($fiItems.MoreAvailable -eq $true)
return $NameList
}
}
function Get-ExistingSteps{
param(
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName,
[Parameter(Position=1, Mandatory=$true)] [Microsoft.Exchange.WebServices.Data.Folder]$QuickStepsFolder
)
Begin
{
$NameList = #{}
$enc = [system.Text.Encoding]::ASCII
$PR_ROAMING_XMLSTREAM = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x7C08,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary);
$psPropset= new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
$psPropset.Add($PR_ROAMING_XMLSTREAM)
#Define ItemView to retrive just 1000 Items
$ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)
$ivItemView.Traversal = [Microsoft.Exchange.WebServices.Data.ItemTraversal]::Associated
$fiItems = $null
do{
$fiItems = $QuickStepsFolder.FindItems($ivItemView)
if($fiItems.Items.Count -gt 0){
[Void]$global:service.LoadPropertiesForItems($fiItems,$psPropset)
foreach($Item in $fiItems.Items){
$propval = $null
if($Item.TryGetProperty($PR_ROAMING_XMLSTREAM,[ref]$propval)){
[XML]$xmlVal = $enc.GetString($propval)
if(!$NameList.ContainsKey($xmlVal.CombinedAction.Name.ToLower())){
$NameList.Add($xmlVal.CombinedAction.Name.Trim().ToLower(),$Item)
}
}
}
}
$ivItemView.Offset += $fiItems.Items.Count
}while($fiItems.MoreAvailable -eq $true)
return $NameList
}
}
#######################
<#
.SYNOPSIS
Gets the existing Outlook Quick Steps from a Mailbox using the Exchange Web Services API
.DESCRIPTION
Gets the existing Outlook Quick Steps from a Mailbox using the Exchange Web Services API
Requires the EWS Managed API from https://www.microsoft.com/en-us/download/details.aspx?id=42951
.EXAMPLE
Example 1 To Gets the existing Outlook Quick Steps from a Mailbox using the Exchange Web Services API
Get-QuickSteps -MailboxName mailbox#domain.com
This returns a HashTable of the QuickSteps to access a Quickstep within the collection use the Index value eg
$QuickSteps = Get-QuickSteps -MailboxName mailbox#domain.com
$QuickSteps["clutter"]
#>
########################
function Get-QuickSteps{
param(
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName
)
Begin{
#Connect
#$service = Connect-Exchange -MailboxName $MailboxName -Credential $Credentials
$QuickStepsFolder = Get-QuickStepsFolder -MailboxName $MailboxName -service $global:service
$ExistingSteps = Get-ExistingStepNames -MailboxName $MailboxName -QuickStepsFolder $QuickStepsFolder
Write-Output $ExistingSteps
}
}
#######################
<#
.SYNOPSIS
Exports an Outlook Quick Step XML settings from a QuickStep Item in a Mailbox using the Exchange Web Services API
.DESCRIPTION
Exports an Outlook Quick Step XML settings from a QuickStep Item in a Mailbox using the Exchange Web Services API
Requires the EWS Managed API from https://www.microsoft.com/en-us/download/details.aspx?id=42951
.EXAMPLE
Example 1 Exports an Outlook Quick Step XML settings from a QuickStep Item in a Mailbox to a file
Export-QuickStepXML -MailboxName mailbox#domain -Name 'Name of QuickStep' -FileName c:\temp\exportFile.xml
#>
########################
function Export-QuickStepXML{
param(
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName,
[Parameter(Position=1, Mandatory=$true)] [string]$Name,
[Parameter(Position=2, Mandatory=$true)] [string]$FileName
)
Begin{
#Connect
#$service = Connect-Exchange -MailboxName $MailboxName -Credential $Credentials
$QuickStepsFolder = Get-QuickStepsFolder -MailboxName $MailboxName -service $global:service
$ExistingSteps = Get-ExistingSteps -MailboxName $MailboxName -QuickStepsFolder $QuickStepsFolder
if($ExistingSteps.ContainsKey($Name.Trim().ToLower())){
$PR_ROAMING_XMLSTREAM = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x7C08,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary);
$psPropset= new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
$psPropset.Add($PR_ROAMING_XMLSTREAM)
$propval = $null
if($ExistingSteps[$Name.Trim().ToLower()].TryGetProperty($PR_ROAMING_XMLSTREAM,[ref]$propval)){
[System.IO.File]::WriteAllBytes($FileName,$propval)
Write-Host ('Exported to ' + $FileName)
}
}
}
}
function Create-QuickStepFromXML
{
param(
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName,
[Parameter(Position=1, Mandatory=$true)] [String]$XMLFileName
)
Begin
{
#Connect
[xml]$QuickStepXML = Get-Content -Path $XMLFileName
$DisplayName = $QuickStepXML.CombinedAction.Name
$Name = $QuickStepXML.CombinedAction.Name.ToLower()
$service = Connect-Exchange -MailboxName $MailboxName
$QuickStepsFolder = Get-QuickStepsFolder -MailboxName $MailboxName -service $service
$QuickStepItem = New-Object Microsoft.Exchange.WebServices.Data.EmailMessage -ArgumentList $global:service
$QuickStepItem.ItemClass = "IPM.Microsoft.CustomAction"
$ExistingSteps = Get-ExistingStepNames -MailboxName $MailboxName -QuickStepsFolder $QuickStepsFolder
if(!$ExistingSteps.ContainsKey($Name.Trim().ToLower())){
$PR_ROAMING_XMLSTREAM = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x7C08,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary);
$enc = [system.Text.Encoding]::ASCII
$QuickStepItem.SetExtendedProperty($PR_ROAMING_XMLSTREAM,$enc.GetBytes((Get-Content -Path $XMLFileName)))
$QuickStepItem.IsAssociated = $true
$QuickStepItem.Save($QuickStepsFolder.Id)
Write-host
Write-host ("SUCCESS Created QuickStep " + $DisplayName)
}
else
{
Write-host
Write-host ("FAILED: QuickStep with name " + $DisplayName + " already exists")
# throw ("Step with Name " + $DisplayName + " already exists")
}
}
}
#######################
<#
.SYNOPSIS
Deletes an Outlook Quick Step from a Mailbox using the Exchange Web Services API
.DESCRIPTION
Deletes an Outlook Quick Step from a Mailbox using the Exchange Web Services API
Requires the EWS Managed API from https://www.microsoft.com/en-us/download/details.aspx?id=42951
.EXAMPLE
Example 1 To Delete an Outlook Quick Step from a Mailbox give the name of the Quickstep
Delete-QuickStep -MailboxName mailbox#domain -Name 'Name of QuickStep'
#>
########################
function Delete-QuickStep{
param(
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName,
[Parameter(Position=1, Mandatory=$true)] [String]$Name
)
Begin
{
#Connect
$service = Connect-Exchange -MailboxName $MailboxName
$QuickStepsFolder = Get-QuickStepsFolder -MailboxName $MailboxName -service $service
$ExistingSteps = Get-ExistingSteps -MailboxName $MailboxName -QuickStepsFolder $QuickStepsFolder
if($ExistingSteps.ContainsKey($Name.Trim().ToLower())){
$yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes",""
$no = New-Object System.Management.Automation.Host.ChoiceDescription "&No",""
$choices = [System.Management.Automation.Host.ChoiceDescription[]]($yes,$no)
$message = "Do you want to Delete QuickStep with Name " + $Name.Trim()
$result = $Host.UI.PromptForChoice($caption,$message,$choices,1)
if($result -eq 0) {
$ExistingSteps[$Name.Trim().ToLower()].Delete([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete)
Write-Host ("QuickStep Deleted")
}
else{
Write-Host ("No Action Taken")
}
}
else{
Write-Host -ForegroundColor Yellow ("No QuickStep found")
}
}
}
########### MAIN #############
$XML_QuickStepsContent=#'
<?xml version="1.0"?>
<CombinedAction Ordinal="200" Tooltip="" Icon="FileSendAsAttachment" Name="Sign as SPAM" Version="154433">
<ActionForwardAsAttach>
<Subject>TEST - PLS IGNORE: <Subject></Subject>
<Location/>
<Body/>
<Send>1</Send>
<FlagDays>0</FlagDays>
<Recipient>
<EntryId>00000000DCA740C8C042101AB4B908002B2FE18201000000000000002F6F3D4D41494C2F6F753D45786368616E67652041646D696E6973747261746976652047726F7570202846594449424F484632335350444C54292F636E3D526563697069656E74732F636E3D4D62782047454152202847654F5320456D61696C20416E616C7973697320616E6420526573706F6E73652962616500</EntryId>
</Recipient>
</ActionForwardAsAttach>
<ActionMoveToFolder>
<Folder>800101012E000000A486FB38C9000000D286FB380000000020C51E61B74A4A44921273CE8E5927E30100293C04FDADC95F4FBF47717FB2F56B5B00000068F0D500000000000038A1BB1005E5101AA1BB08002B2A56C20000454D534D44422E444C4C00000000000000001B55FA20AA6611CD9BC800AA002FC45A0C00000033323136343731352D343937622D346665382D623535632D3731323539636665646264624065787465726E616C2E656E692E636F6D002F6F3D4D41494C2F6F753D45786368616E67652041646D696E6973747261746976652047726F7570202846594449424F484632335350444C54292F636E3D526563697069656E74732F636E3D434F323933353065333400</Folder>
</ActionMoveToFolder>
</CombinedAction>
'#
try {
$mailAdddress, $username = getCurrentUserMailboxAndName
$res = read-host "Create quick steps for ${username} mailbox ${mailAdddress}? [y|N]"
if ("y" -eq $res.toLower()) {
$tmpFile = New-TemporaryFile
Out-File -FilePath $tmpFile.FullName -InputObject $XML_QuickStepsContent
Create-QuickStepFromXML $mailAdddress -XMLFileName $tmpFile.FullName
Remove-Item $tmpFile
} else {
Write-Host
Write-Host "Nothing to do"
}
} catch {
Write-Host
Write-Host "ERROR: $($_.exception)"
}
Write-Host
$close = read-host "Press Enter to finish"
Write-Host
Write-Host "Script terminated" + $close ``
There are a few ways you could do it probably the easiest is to use the ADAL.dll's eg
Write-Verbose("Using Modern Auth")
if ([String]::IsNullOrEmpty($ClientId)) {
$ClientId = "d3590ed6-52b3-4102-aeff-aad2292ab01c"
}
Import-Module ($script:ModuleRoot + "/bin/Microsoft.IdentityModel.Clients.ActiveDirectory.dll") -Force
$Context = New-Object Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext("https://login.microsoftonline.com/common")
if ($Credentials -eq $null) {
$PromptBehavior = New-Object Microsoft.IdentityModel.Clients.ActiveDirectory.PlatformParameters -ArgumentList Auto
$token = ($Context.AcquireTokenAsync("https://outlook.office365.com", $ClientId , "urn:ietf:wg:oauth:2.0:oob", $PromptBehavior)).Result
$service.Credentials = New-Object Microsoft.Exchange.WebServices.Data.OAuthCredentials($token.AccessToken)
}else{
$AADcredential = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.UserPasswordCredential" -ArgumentList $Credentials.UserName.ToString(), $Credentials.GetNetworkCredential().password.ToString()
$token = [Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContextIntegratedAuthExtensions]::AcquireTokenAsync($Context,"https://outlook.office365.com",$ClientId,$AADcredential).result
$service.Credentials = New-Object Microsoft.Exchange.WebServices.Data.OAuthCredentials($token.AccessToken)
}
A few other examples https://ingogegenwarth.wordpress.com/2018/08/02/ews-and-oauth/ and https://gsexdev.blogspot.com/2019/10/using-msal-microsoft-authentication.html

Set WMI permissions for 'Domain users'

I've created two functions in PowerShell, one that creates the Namespace ROOT\CustomCMClasses and one that creates the class Test. This piece works fine:
Param (
$Namespace = 'CustomCMClasses',
$Class = 'Test'
)
Function New-WMINamespaceHC{
if (Get-WmiObject -Namespace 'root' -Class '__NAMESPACE' | Where-Object {$_.Name -eq $Namespace}) {
Write-Verbose "WMI Namespace 'root\$Namespace' exists"
}
else {
Write-Verbose "Create WMI namespace 'root\$Namespace'"
$Ns = [WMICLASS]'root:__Namespace'
$NewNamespace = $Ns.createInstance()
$NewNamespace.Name = $Namespace
$NewNamespace.Put()
}
}
Function New-WMIClassHC {
if (Get-WmiObject -List -Namespace "root\$Namespace" | Where-Object {$_.Name -eq $Class}) {
Write-Verbose "WMI Class '$Class' exists"
}
else {
Write-Verbose "Create WMI Class '$Class'"
$NewClass = New-Object System.Management.ManagementClass ("root\$Namespace", [String]::Empty, $Null);
$NewClass['__CLASS'] = $Class
$NewClass.Qualifiers.Add('Static', $true)
$NewClass.Properties.Add('Key', [System.Management.CimType]::String, $false)
$NewClass.Properties['Key'].Qualifiers.Add('Key', $true)
$NewClass.Properties.Add('Value1', [System.Management.CimType]::UInt32, $false)
$NewClass.Properties.Add('Value2', [System.Management.CimType]::String, $false)
$NewClass.Put()
}
}
At this point I need to grant Domain users permissions on the new namespace, so they are able to write data to it on their local client (no remoting needed). This is the point where I find a lot of information but am now stuck.
On the Microsoft blog there is a script I tried to tweak, but it's overly complicated for my needs and it failed so I found and tweaked the following code:
Function Add-WMIPermissions {
[CmdLetBinding()]
Param (
[String]$Principal = 'DOMAIN.NET\Domain users',
[String]$Namespace = 'CustomCMClasses'
)
Function Get-Sid {
Param (
$DSIdentity
)
$ID = new-object System.Security.Principal.NTAccount($DSIdentity)
Return $ID.Translate([System.Security.Principal.SecurityIdentifier]).toString()
}
$Sid = Get-Sid $Principal
$WMISDDL = "A;CI;CCWP;;;$Sid"
$WMISDDLPartialMatch = "A;\w*;\w+;;;$Sid"
$security = Get-WmiObject -Namespace root\$Namespace -Class __SystemSecurity
$binarySD = #($null)
$result = $security.PsBase.InvokeMethod('GetSD',$binarySD)
$converter = New-Object system.management.ManagementClass Win32_SecurityDescriptorHelper
$CurrentWMISDDL = $converter.BinarySDToSDDL($binarySD[0])
if (($CurrentWMISDDL.SDDL -match $WMISDDLPartialMatch) -and ($CurrentWMISDDL.SDDL -notmatch $WMISDDL)) {
$NewWMISDDL = $CurrentWMISDDL.SDDL -replace $WMISDDLPartialMatch, $WMISDDL
}
else {
$NewWMISDDL = $CurrentWMISDDL.SDDL += '(' + $WMISDDL + ')'
}
$WMIbinarySD = $converter.SDDLToBinarySD($NewWMISDDL)
$WMIconvertedPermissions = ,$WMIbinarySD.BinarySD
if ($CurrentWMISDDL.SDDL -match $WMISDDL) {
Write-Verbose 'Current WMI Permissions matches desired value'
}
else {
$result = $security.PsBase.InvokeMethod('SetSD',$WMIconvertedPermissions)
if($result='0'){
Write-Verbose 'WMI permissions applied'
}
}
}
Add-WMIPermissions -Verbose
It's clearly stating that the permissions are correctly applied but the user still can't write data to WMI. Working with WMI is new to me, so any help is greatly appreciated.
The test code to see if a regular user (Domain users) has permissions:
$WMIClass = [WMICLASS]('root\' + $Namespace + ':' + $Class)
$WMIInstance = $WMIClass.CreateInstance()
$WMIInstance.Key = 'Unique value identifier 5'
$WMIInstance.Value1 = 101
$WMIInstance.Value2 = 'Status Ok'
$WMIInstance.Put()
Solved the problem for Partial Write:
Function Set-WMIPermissionsHC {
Param (
[String]$Namespace = 'CustomCMClasses',
[String]$Class = 'Test',
[String]$Account = 'DOMAIN\Domain users',
[String]$Computer = $env:COMPUTERNAME
)
Function Get-Sid {
Param (
$Account
)
$ID = New-Object System.Security.Principal.NTAccount($Account)
Return $ID.Translate([System.Security.Principal.SecurityIdentifier]).toString()
}
$SID = Get-Sid $Account
$SDDL = "A;CI;CCSWWP;;;$SID"
$DCOMSDDL = "A;;CCDCRP;;;$SID"
$Reg = [WMICLASS]"\\$Computer\root\default:StdRegProv"
$DCOM = $Reg.GetBinaryValue(2147483650,'software\microsoft\ole','MachineLaunchRestriction').uValue
$Security = Get-WmiObject -ComputerName $Computer -Namespace "root\$Namespace" -Class __SystemSecurity
$Converter = New-Object System.Management.ManagementClass Win32_SecurityDescriptorHelper
$BinarySD = #($null)
$Result = $Security.PsBase.InvokeMethod('GetSD', $BinarySD)
$OutSDDL = $Converter.BinarySDToSDDL($BinarySD[0])
$OutDCOMSDDL = $Converter.BinarySDToSDDL($DCOM)
$NewSDDL = $OutSDDL.SDDL += '(' + $SDDL + ')'
$NewDCOMSDDL = $OutDCOMSDDL.SDDL += '(' + $DCOMSDDL + ')'
$WMIbinarySD = $Converter.SDDLToBinarySD($NewSDDL)
$WMIconvertedPermissions = ,$WMIbinarySD.BinarySD
$DCOMbinarySD = $Converter.SDDLToBinarySD($NewDCOMSDDL)
$DCOMconvertedPermissions = ,$DCOMbinarySD.BinarySD
$Result = $Security.PsBase.InvokeMethod('SetSD', $WMIconvertedPermissions)
$Result = $Reg.SetBinaryValue(2147483650,'software\microsoft\ole','MachineLaunchRestriction', $DCOMbinarySD.binarySD)
Write-Verbose 'WMI Permissions set'
}
Thanks to this blog and the Microsoft blog for explaining the permissions

get lan id expiration as a regular user not admin

import-module ActiveDirectory
(get-aduser "djohns02" -Properties AccountExpirationDate).AccountExpirationDate
This would work if the user had ADUC tools setup and configured on their machines. The problem is users will not have this setup and installed.
My next thought would be to try and setup a powershell script to run a remote script on a machine that does have this setup. Since they are not admins, they cannot get this setup and working as far as I know. As an admin I could force the trust and enable remote, but not as a standard user.
Are they any possibilities I am not thinking of? The goal is for contractors to be able to check when their account expires.
net user %USERNAME% /domain
My program can filter the results and give me just the expiration
Here is a PowerShell script you can use that does not require the AD cmdlets:
# Get-AccountExpirationDate.ps1
# Written by Bill Stewart
#requires -version 2
[CmdletBinding()]
param(
[parameter(Mandatory=$true,ValueFromPipeline=$true)]
[String[]] $Name
)
begin {
$ACCOUNT_NEVER_EXPIRES = 0x7FFFFFFFFFFFFFFF
$Searcher = [ADSISearcher] [ADSI] ""
$Searcher.PageSize = 1000
$Searcher.PropertiesToLoad.AddRange(#("accountexpires","distinguishedname","samaccountname"))
function Get-SearchResultProperty {
param(
[System.DirectoryServices.ResultPropertyCollection] $properties,
[String] $propertyName
)
if ( $properties[$propertyName] ) {
$properties[$propertyName][0]
}
}
function Get-AccountExpirationDate {
param(
$name
)
$Searcher.Filter = "(&(objectCategory=person)(objectClass=user)(anr=$name))"
$searchResults = $searcher.FindAll()
$resultCount = ($searchResults | measure-object).Count
if ( $resultcount -gt 0 ) {
foreach ( $searchResult in $searchResults ) {
$properties = $searchResult.Properties
$accountExpires = Get-SearchResultProperty $properties "accountexpires"
if ( $accountExpires -ne $null ) {
if ( ($accountExpires -eq 0) -or ($accountExpires -eq $ACCOUNT_NEVER_EXPIRES) ) {
$accountExpires = $null
}
else {
$accountExpires = [DateTime]::FromFileTime($accountExpires)
}
}
"" | select-object `
#{Name = "distinguishedName"; Expression = {Get-SearchResultProperty $properties "distinguishedname"}},
#{Name = "sAMAccountName"; Expression = {Get-SearchResultProperty $properties "samaccountname"}},
#{Name = "accountExpires"; Expression = {$accountExpires}}
}
}
$searchResults.Dispose()
}
}
process {
foreach ( $nameItem in $Name ) {
Get-AccountExpirationDate $nameItem
}
}
You can run it like this (assuming the script is in C:\Scripts):
PS C:\Scripts> .\Get-AccountExpirationDate.ps1 kendyer

PowerShell DSC: Error 22 using certs to encrypt creds in a Service Resource

I'm trying to use certificates to embed credentials into a Service resource. I've got PKI in the infrastructure and all my test servers are auto-enrolled. I exported their certs locally to work with and have them in my ConfigData as follows:
#{
AllNodes = #(
#{
NodeName = "*"
NeoConfigDestinationPath = "D:\ServerBox\Servers\JRun4\_build\shared\config"
}
#{
NodeName = 'DEVOPS'
Role = #('DSCPullServer')
CertificateFile = "D:\EQ_DSCModule\Certs\DEVOPS.cer"
Thumbprint = "AE4F10AE4141C8726EEEBE888C69FE7ABB3099A8"
}
#{
NodeName = 'Server1'
Role = #('IIS', 'ServerBox', 'DevInt')
CFServices = #("Adobe CF9 1", "Adobe CF9 2", "Adobe CF9 3", "Adobe CF9 4")
CertificateFile = "D:\EQ_DSCModule\Certs\Client1.cer"
Thumbprint = "4FA343A76AEA2B805850190E9C04AA9E2A82A162"
}
#{
NodeName = 'Server2'
Role = #('IIS', 'ServerBox', 'DevInt')
CFServices = #("Adobe CF9 1")
CertificateFile = "D:\EQ_DSCModule\Certs\Client2.cer"
Thumbprint = "0FCB76684F0C74495DEB54F637B50BDA7182483D"
}
)
ServerBoxConfig = #{
SourcePath = "\\Share\Path\DevOps\ServerBox"
DestinationPath = "D:\ServerBox"
}
DevIntConfig = #{
SourcePath = "\\Share\Path\DevOps\DevInt"
DestinationPath = "D:\ServerBox\IIS\wwwroot"
NeoConfigSourcePath = "\\Share\Path\DevOps\ServerConfig\Environments\DevInt\NeoConfig"
}
}
This this is the config script that I'm running:
$webCFDevCred = Get-Credential -Credential "svc-webcfdev#domain.com"
Configuration EqConfig
{
Import-DSCResource -Module xPSDesiredStateConfiguration
Import-DSCResource -Module cChoco
Node $AllNodes.NodeName {
cChocoInstaller installChoco {
InstallDir = "C:\ProgramData\Chocolatey"
}
}
Node $AllNodes.Where({ $_.role -eq 'DSCPullServer' }).NodeName { ... } #DSCPullServer
Node $AllNodes.Where({ $_.role -eq 'IIS' }).NodeName { ... } #IIS
Node $AllNodes.Where({ $_.role -eq 'ServerBox' }).NodeName {
File ServerBox
{
Ensure = "Present"
Type = "Directory"
Recurse = $true
MatchSource = $true
Force = $true
Checksum = "modifiedDate"
SourcePath = $ConfigurationData.ServerBoxConfig.SourcePath
DestinationPath = $ConfigurationData.ServerBoxConfig.DestinationPath
}
} #ServerBox
Node $AllNodes.Where({ $_.role -eq 'DevInt' }).NodeName {
File DevInt
{
Ensure = "Present"
Type = "Directory"
Recurse = $true
MatchSource = $true
Force = $true
Checksum = "modifiedDate"
SourcePath = $ConfigurationData.DevIntConfig.SourcePath
DestinationPath = $ConfigurationData.DevIntConfig.DestinationPath
DependsOn = "[File]ServerBox"
}
File DevInt_Config
{
Ensure = "Present"
Type = "Directory"
MatchSource = $true
Force = $true
Checksum = "modifiedDate"
SourcePath = $ConfigurationData.DevIntConfig.NeoConfigSourcePath
DestinationPath = $Node.NeoConfigDestinationPath
DependsOn = "[File]ServerBox"
}
#This runs a script to build out the ColdFusion cluster/servers
#Uses the number of services as the param for serverCount
cChocoPackageInstaller installServerBox {
Name = "ServerBox.DevInt -params $($Node.CFServices.Length)"
DependsOn = #("[cChocoInstaller]installChoco", "[File]DevInt_Config")
}
#Sets the services generated by the ServerBox script
$Node.CFServices.ForEach({
Service "Service-$_" {
Name = $_
State = 'Running'
Credential = $webCFDevCred
DependsOn = "[cChocoPackageInstaller]installServerBox"
}
})
} #DevInt
} #Configuration
EqConfig -ConfigurationData .\EQConfigData.psd1 -Output .\EqConfig -Verbose
Function Get-ComputerGuid
{
param (
[Parameter(Mandatory = $true)]
[string]$ComputerName
)
process
{
([guid]([adsisearcher]"(samaccountname=$ComputerName`$)").FindOne().Properties["objectguid"][0]).Guid
}
}
$DSCPullFolder = "C:\Program Files\WindowsPowerShell\DscService\Configuration"
Get-ChildItem .\EqConfig\* -Filter *.mof | ForEach-Object {
$guidMofFile = "$DSCPullFolder\$(Get-ComputerGuid $_.BaseName).mof"
$newMof = copy $_.FullName $guidMofFile -PassThru -Force
$newHash = (Get-FileHash $newMof).hash
[System.IO.File]::WriteAllText("$newMof.checksum", $newHash)
}
Configuration EqLocalConfig
{
Node $AllNodes.NodeName {
LocalConfigurationManager {
AllowModuleOverwrite = 'True'
CertificateID = $Node.Thumbprint
ConfigurationID = $(Get-ComputerGuid $nodeName)
ConfigurationModeFrequencyMins = 15
ConfigurationMode = 'ApplyAndAutoCorrect'
RebootNodeIfNeeded = 'True'
RefreshMode = 'PULL'
DownloadManagerName = 'WebDownloadManager'
DownloadManagerCustomData = (#{ ServerUrl = "https://DEVOPS:443/psdscpullserver.svc" })
}
}
}
EqLocalConfig -ConfigurationData .\EQConfigData.psd1 -Verbose
Set-DscLocalConfigurationManager -Path .\EqLocalConfig -Verbose
As far as I can tell it should work. My MOFs get generated with encrypted passwords inside, but when the client servers pick up the config and get to the Service step, it errors out. Checking the event viewer this is the details on the event:
"This event indicates that failure happens when LCM is processing the configuration. ErrorId is 0x1. ErrorDetail is The SendConfigurationApply function did not succeed.. ResourceId is [Service]Service-Adobe CF9 1 and SourceInfo is D:\EQ_DSCModule\EqConfig.ps1::285::4::Service. ErrorMessage is PowerShell provider MSFT_ServiceResource failed to execute Set-TargetResource functionality with error message: Failed to change 'Credential' property. Message: 'The 'Change' method of 'Win32_Service' failed with error code: '22'.' ."
According to MSDN (https://msdn.microsoft.com/en-us/library/aa384901%28v=vs.85%29.aspx) error code 22 on the Change method means "The account under which this service runs is either invalid or lacks the permissions to run the service." I know the service account works fine and I can add it myself using WMI as follows:
For ($i=0; $i -lt $clusterCount; $i++) {
(Get-WmiObject -Query "SELECT * FROM Win32_Service WHERE Name = 'Adobe CF9 $($i+1)'").Change($null,$null,$null,$null,$null,$null,'svc-webcfdev#domain.com','password',$null,$null,$null)
}
So if I can add the account using WMI, DSC should be able to as well, right? Ugh!
Ideas?
When you use both certificate files and thumbprint (certificateid) the encryption will happen with the certificate file but only the thumbprint (certificateid) you entered gets written to the .mof file. They can easily get out of sync. As a test try adding the certificates to the local machine store and then remove the reference to the certificate files from the script. Regenerate and fix any issues if a certificate can't be found. See if that fixes the issue.