Powershell Remote Registry Access to Non-Domain Server - powershell

I have the following script which I use to collect the installed applications on various servers:
$results = #()
$list = Get-Content serverlist.txt
foreach ($computer in $list) {
echo "Processing $($computer)"
if (Test-Connection -Cn $computer -BufferSize 16 -Count 1 -ea 0 -quiet ) {
echo "Getting installed apps from $($computer) ..."
$UninstallKey="SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall"
echo "Opening remote registry ..."
$reg=[microsoft.win32.registrykey]::OpenRemoteBaseKey('LocalMachine',$computer)
echo "Getting registry keys ..."
if ($reg) {
$regkey=$reg.OpenSubKey($UninstallKey)
if ($regkey) {
$subkeys=$regkey.GetSubKeyNames()
echo "Building result object ..."
foreach($key in $subkeys){
$thisKey=$UninstallKey+"\\"+$key
$thisSubKey=$reg.OpenSubKey($thisKey)
$obj = New-Object PSObject
$obj | Add-Member -MemberType NoteProperty -Name "ComputerName" -Value $computer
$obj | Add-Member -MemberType NoteProperty -Name "DisplayName" -Value $($thisSubKey.GetValue("DisplayName"))
$obj | Add-Member -MemberType NoteProperty -Name "DisplayVersion" -Value $($thisSubKey.GetValue("DisplayVersion"))
$obj | Add-Member -MemberType NoteProperty -Name "InstallLocation" -Value $($thisSubKey.GetValue("InstallLocation"))
$obj | Add-Member -MemberType NoteProperty -Name "Publisher" -Value $($thisSubKey.GetValue("Publisher"))
$results += $obj
}
}
}
} else {
echo "$($computer) is DOWN!"
$obj = New-Object PSObject
$obj | Add-Member -MemberType NoteProperty -Name "ComputerName" -Value $computer
$obj | Add-Member -MemberType NoteProperty -Name "DisplayName" -Value "Down"
$obj | Add-Member -MemberType NoteProperty -Name "DisplayVersion" -Value "Down"
$obj | Add-Member -MemberType NoteProperty -Name "InstallLocation" -Value "Down"
$obj | Add-Member -MemberType NoteProperty -Name "Publisher" -Value "Down"
$results += $obj
}
}
$results | Where-Object { $_.DisplayName } | select ComputerName, DisplayName, DisplayVersion, InstallLocation, Publisher | Export-Csv D:\Temp\InstalledApps.csv
This works fine on all of the domain member servers. The issue comes when I try to get this to work on two non-domain Windows 2008R2 Standard servers. One server works fine and the other server does not. On the server that does not work, I get the following error:
Exception calling "OpenSubKey" with "1" argument(s): "Requested registry access is not allowed."
I cannot figure out why this would work for one server and not the other. I am running the script from a non-elevated Powershell on a Windows 7 x64 machine.
UPDATE:
I used Process Monitor to track what the Remote Registry service was doing on both machines. On the machine where things work OK, I see 4 reads of HKLM\System\CurrentControlSet\Control\SecurePipeServers\winreg and then it starts pulling the information I want from HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall. On the machine where things are not working, Remote Registry does the same 4 reads of the winreg key but then it starts reading HKLM\System\CurrentControlSet\Control\SecurePipeServers\winreg\AllowedPaths and HKLM\System\CurrentControlSet\Control\SecurePipeServers\winreg\AllowedExactPaths. I did some investigation and Remote Registry checks the permissions on the winreg key before proceeding. I compared the good machine and bad machine and the registry keys and permissions are all exactly the same and are the default ones installed.

I did not find an actual cause or solution to this issue but I did find a workaround. Adding the uninstall registry keys to the AllowedPaths entry allowed me to do what I want. Not sure why one server requires this and the other doesn't but I can move ahead this way.

Related

Intermittent error messages when running an Azure Functions app

I have a Powershell program which runs on a schedule in an Azure Functions app. It connects to Office 365 to download audit logs, make some changes and then export a CSV to an Azure Data Lake Storage account. To avoid hard-coded credentials, an Azure Key Vault stores the secrets. I created a managed identity in the Azure Function along with the required application settings and URL to point to Azure Key Vault. The code references the application secrets (APPSETTING) and all seemed to be running well, until I noticed today that since yesterday afternoon the exported CSV files were empty.
So I opened up the Function app, hit Run manually and I could see a CSV file exported with data. When I took a look at the execution log however, I spotted these error messages which despite not affecting the execution this time, makes me wonder whether this is what caused the problem with the empty CSV files. The program is now running on a schedule as normal and the error messages appear to be intermittent.
Not sure why it's complaining about the username and password, when it is clearly able to access the data source (Office audit logs), export the CSV and transfer it to the file destination (Azure Data Lake Storage) successfully.
Any idea what is going on? Any tips or suggestions welcome! Code provided below. Many thanks!
# Input bindings are passed in via param block.
param($Timer)
# Get the current universal time in the default string format.
$currentUTCtime = (Get-Date).ToUniversalTime()
# The 'IsPastDue' property is 'true' when the current function invocation is later than scheduled.
if ($Timer.IsPastDue) {
Write-Host "PowerShell timer is running late!"
}
# Write an information log with the current time.
Write-Host "PowerShell timer trigger function ran! TIME: $currentUTCtime"
<#
Title: Power BI Audit Logging
Client:
Description: Connects to Azure audit logs using admin credentials (secrets via Azure Key Vault). Opens a session to iterate through the Audit Log ($currentrResults) and aggregate
the logs into a single object ($aggregateResults). A for-each loop then iterates through the $aggregateResults and assigns each data piece (datum)
to a PowerShell object to which properties are added to hold the audit data. A CSV file is created and exported, and then transferred to a Data Lake storage account (using SAS secret via Azure Key Vault).
Last Revision: 06/09/2020 #>
Set-ExecutionPolicy RemoteSigned
Set-Item ENV:\SuppressAzurePowerShellBreakingChangeWarnings "true"
# Better for scheduled jobs
$uSecret = $ENV:APPSETTING_SecretUsername
$pSecret = $ENV:APPSETTING_SecretPassword
$sasSecret = $ENV:APPSETTING_SecretSAS
$securePassword = ConvertTo-SecureString -String $pSecret -AsPlainText -Force
$UserCredential = New-Object -TypeName "System.Management.Automation.PSCredential" -ArgumentList $uSecret, $securePassword
# This will prompt the user for credential (optional)
# $UserCredential = Get-Credential
$session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $UserCredential -Authentication Basic -AllowRedirection
Import-PSSession $session
$startDate=(get-date).AddDays(-10)
$endDate=(get-date)
$scriptStart=(get-date)
$sessionName = (get-date -Format 'u')+'pbiauditlog'
# Reset user audit accumulator
$aggregateResults = #()
$i = 0 # Loop counter
Do {
$currentResults = Search-UnifiedAuditLog -StartDate $startDate -EndDate $enddate -SessionId $sessionName -SessionCommand ReturnLargeSet -ResultSize 1000 -RecordType PowerBIAudit
if ($currentResults.Count -gt 0) {
Write-Host ("Finished {3} search #{1}, {2} records: {0} min" -f [math]::Round((New-TimeSpan -Start $scriptStart).TotalMinutes,4), $i, $currentResults.Count, $user.UserPrincipalName )
# Accumulate the data.
$aggregateResults += $currentResults
# No need to do another query if the # records returned <1000 - should save around 5-10 seconds per user.
if ($currentResults.Count -lt 1000) {
$currentResults = #()
} else {
$i++
}
}
} Until ($currentResults.Count -eq 0) # End of Session Search Loop.
$data=#()
foreach ($auditlogitem in $aggregateResults) {
$datum = New-Object -TypeName PSObject
$d = ConvertFrom-json $auditlogitem.AuditData
$datum | Add-Member -MemberType NoteProperty -Name Id -Value $d.Id
$datum | Add-Member -MemberType NoteProperty -Name CreationTDateTime -Value $d.CreationDate
$datum | Add-Member -MemberType NoteProperty -Name CreationTime -Value $d.CreationTime
$datum | Add-Member -MemberType NoteProperty -Name RecordType -Value $d.RecordType
$datum | Add-Member -MemberType NoteProperty -Name Operation -Value $d.Operation
$datum | Add-Member -MemberType NoteProperty -Name OrganizationId -Value $d.OrganizationId
$datum | Add-Member -MemberType NoteProperty -Name UserType -Value $d.UserType
$datum | Add-Member -MemberType NoteProperty -Name UserKey -Value $d.UserKey
$datum | Add-Member -MemberType NoteProperty -Name Workload -Value $d.Workload
$datum | Add-Member -MemberType NoteProperty -Name UserId -Value $d.UserId
$datum | Add-Member -MemberType NoteProperty -Name ClientIPAddress -Value $d.ClientIPAddress
$datum | Add-Member -MemberType NoteProperty -Name UserAgent -Value $d.UserAgent
$datum | Add-Member -MemberType NoteProperty -Name Activity -Value $d.Activity
$datum | Add-Member -MemberType NoteProperty -Name ItemName -Value $d.ItemName
$datum | Add-Member -MemberType NoteProperty -Name WorkSpaceName -Value $d.WorkSpaceName
$datum | Add-Member -MemberType NoteProperty -Name DashboardName -Value $d.DashboardName
$datum | Add-Member -MemberType NoteProperty -Name DatasetName -Value $d.DatasetName
$datum | Add-Member -MemberType NoteProperty -Name ReportName -Value $d.ReportName
$datum | Add-Member -MemberType NoteProperty -Name WorkspaceId -Value $d.WorkspaceId
$datum | Add-Member -MemberType NoteProperty -Name ObjectId -Value $d.ObjectId
$datum | Add-Member -MemberType NoteProperty -Name DashboardId -Value $d.DashboardId
$datum | Add-Member -MemberType NoteProperty -Name DatasetId -Value $d.DatasetId
$datum | Add-Member -MemberType NoteProperty -Name ReportId -Value $d.ReportId
$datum | Add-Member -MemberType NoteProperty -Name OrgAppPermission -Value $d.OrgAppPermission
# Option to include the below JSON column however for large amounts of data it may be difficult for PBI to parse
$datum | Add-Member -MemberType NoteProperty -Name Datasets -Value (ConvertTo-Json $d.Datasets)
# Below is a simple PowerShell statement to grab one of the entries and place in the DatasetName if any exist
foreach ($dataset in $d.datasets) {
$datum.DatasetName = $dataset.DatasetName
$datum.DatasetId = $dataset.DatasetId
}
$data+=$datum
}
$dateTimestring = $startDate.ToString("yyyyMMdd") + "_" + (Get-Date -Format "yyyyMMdd") + "_" + (Get-Date -Format "HHmm")
$fileName = ($dateTimestring + ".csv")
Write-Host ("Writing to file {0}" -f $fileName)
$filePath = "$Env:temp/" + $fileName
$data | Export-csv -Path $filePath
# File transfer to Azure storage account
Get-AzContext #Connect-AzAccount -Credential $UserCredential
Get-AzVM -ResourceGroupName "Audit" -status
$Context = New-AzStorageContext -StorageAccountName "auditingstorage" -StorageAccountKey $sasSecret
Set-AzStorageBlobContent -Force -Context $Context -Container "auditlogs" -File $filePath -Blob $filename
# Close PowerShell session
Remove-PSSession -Id $Session.Id
Your error state
ERROR: Connect-AzAccount : Username + Password authentication is not
supported in PowerShell Core. Please use device code authentication
for interactive log in, or Service Principal authentication for script
log in.
The problem come from using the credential authentication scheme in Powershell Core
Connect-AzAccount -Credential $UserCredential
Instead, in your app, enable the System Managed Identity and grant it the permissions to access what you need.
You can do that by going into the Identity pane and turning the status to On in the System assigned tab.
From there, add the required access through the Azure role assignments button.
Once this is done, you don't need to use Connect-AzAccount, your app is connected automatically to the managed identity at runtime. You can use Object ID from the Identity pane to find it afterward in Azure Active Directory / App Registration and assign it additional API access if needed.
Additional note
You could always continue to use Connect-AzAccount with a service principal account but unless you have requirements for that, I'd go the Managed Identity route.
References
How to use managed Identities for App Service and Azure Functions
Create an Azure service principal with Azure Powershell

How to reference application settings (key vault references) in Azure Function using Powershell

I am writing a small program in Powershell which connects to Office 365 to download audit logs, make some changes and then export a CSV to an Azure Data Lake Storage account. To run this process on a schedule, I have created an Azure Function app (timer template) to run the program. To avoid hard-coded credentials, I created an Azure Key Vault to store the credential secrets. I created a managed identity in the Azure Function, created the secrets in Azure Key Vault with the credentials and then created three application settings in Azure Function under "Configuration" with the URL to point at the secrets stored in Azure Key Vault.
The three application settings are called "SecretUsername", "SecretPassword" (to point to the Office 365) and "SecretSAS" (to store the CSV in ADLS).
How do I refer to these variables in my Powershell script? I have tried different variations in my code, but none appear to work. Examples:
$uSecret = $SecretUsername
$uSecret = $ENV:SecretUsername
$uSecret = ENV:SecretUsername
$uSecret = (Get-ChildItem ENV:SecretUsername).SecretValueText
# Input bindings are passed in via param block.
param($Timer)
# Get the current universal time in the default string format.
$currentUTCtime = (Get-Date).ToUniversalTime()
# The 'IsPastDue' property is 'true' when the current function invocation is later than scheduled.
if ($Timer.IsPastDue) {
Write-Host "PowerShell timer is running late!"
}
# Write an information log with the current time.
Write-Host "PowerShell timer trigger function ran! TIME: $currentUTCtime"
Set-ExecutionPolicy AllSigned
Set-Item ENV:\SuppressAzurePowerShellBreakingChangeWarnings "true"
$uSecret = (Get-ChildItem ENV:SecretUsername).SecretValueText
$pSecret = (Get-ChildItem ENV:SecretPassword).SecretValueText
$sasSecret = (Get-ChildItem ENV:SecretSAS).SecretValueText
$securePassword = ConvertTo-SecureString -String $pSecret -AsPlainText -Force
$UserCredential = New-Object -TypeName "System.Management.Automation.PSCredential" -ArgumentList $uSecret, $securePassword
$session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $UserCredential -Authentication Basic -AllowRedirection
Import-PSSession $session
$startDate=(get-date).AddDays(-10)
$endDate=(get-date)
$scriptStart=(get-date)
$sessionName = (get-date -Format 'u')+'pbiauditlog'
$aggregateResults = #()
$i = 0 # Loop counter
Do {
$currentResults = Search-UnifiedAuditLog -StartDate $startDate -EndDate $enddate -SessionId $sessionName -SessionCommand ReturnLargeSet -ResultSize 1000 -RecordType PowerBIAudit
if ($currentResults.Count -gt 0) {
Write-Host ("Finished {3} search #{1}, {2} records: {0} min" -f [math]::Round((New-TimeSpan -Start $scriptStart).TotalMinutes,4), $i, $currentResults.Count, $user.UserPrincipalName )
# Accumulate the data.
$aggregateResults += $currentResults
# No need to do another query if the # records returned <1000 - should save around 5-10 seconds per user.
if ($currentResults.Count -lt 1000) {
$currentResults = #()
} else {
$i++
}
}
} Until ($currentResults.Count -eq 0) # End of Session Search Loop.
$data=#()
foreach ($auditlogitem in $aggregateResults) {
$datum = New-Object -TypeName PSObject
$d = ConvertFrom-json $auditlogitem.AuditData
$datum | Add-Member -MemberType NoteProperty -Name Id -Value $d.Id
$datum | Add-Member -MemberType NoteProperty -Name CreationTDateTime -Value $d.CreationDate
$datum | Add-Member -MemberType NoteProperty -Name CreationTime -Value $d.CreationTime
$datum | Add-Member -MemberType NoteProperty -Name RecordType -Value $d.RecordType
$datum | Add-Member -MemberType NoteProperty -Name Operation -Value $d.Operation
$datum | Add-Member -MemberType NoteProperty -Name OrganizationId -Value $d.OrganizationId
$datum | Add-Member -MemberType NoteProperty -Name UserType -Value $d.UserType
$datum | Add-Member -MemberType NoteProperty -Name UserKey -Value $d.UserKey
$datum | Add-Member -MemberType NoteProperty -Name Workload -Value $d.Workload
$datum | Add-Member -MemberType NoteProperty -Name UserId -Value $d.UserId
$datum | Add-Member -MemberType NoteProperty -Name ClientIPAddress -Value $d.ClientIPAddress
$datum | Add-Member -MemberType NoteProperty -Name UserAgent -Value $d.UserAgent
$datum | Add-Member -MemberType NoteProperty -Name Activity -Value $d.Activity
$datum | Add-Member -MemberType NoteProperty -Name ItemName -Value $d.ItemName
$datum | Add-Member -MemberType NoteProperty -Name WorkSpaceName -Value $d.WorkSpaceName
$datum | Add-Member -MemberType NoteProperty -Name DashboardName -Value $d.DashboardName
$datum | Add-Member -MemberType NoteProperty -Name DatasetName -Value $d.DatasetName
$datum | Add-Member -MemberType NoteProperty -Name ReportName -Value $d.ReportName
$datum | Add-Member -MemberType NoteProperty -Name WorkspaceId -Value $d.WorkspaceId
$datum | Add-Member -MemberType NoteProperty -Name ObjectId -Value $d.ObjectId
$datum | Add-Member -MemberType NoteProperty -Name DashboardId -Value $d.DashboardId
$datum | Add-Member -MemberType NoteProperty -Name DatasetId -Value $d.DatasetId
$datum | Add-Member -MemberType NoteProperty -Name ReportId -Value $d.ReportId
$datum | Add-Member -MemberType NoteProperty -Name OrgAppPermission -Value $d.OrgAppPermission
# Option to include the below JSON column however for large amounts of data it may be difficult for PBI to parse
$datum | Add-Member -MemberType NoteProperty -Name Datasets -Value (ConvertTo-Json $d.Datasets)
# Below is a simple PowerShell statement to grab one of the entries and place in the DatasetName if any exist
foreach ($dataset in $d.datasets) {
$datum.DatasetName = $dataset.DatasetName
$datum.DatasetId = $dataset.DatasetId
}
$data+=$datum
}
$dateTimestring = $startDate.ToString("yyyyMMdd") + "_" + (Get-Date -Format "yyyyMMdd") + "_" + (Get-Date -Format "HHmm")
$fileName = ($dateTimestring + ".csv")
Write-Host ("Writing to file {0}" -f $fileName)
$filePath = "$Env:temp/" + $fileName
$data | Export-csv -Path $filePath
Connect-AzAccount -Credential $UserCredential
Get-AzVM -ResourceGroupName "Audit" -status
$Context = New-AzStorageContext -StorageAccountName "auditingstorage" -StorageAccountKey $sasSecret
Set-AzStorageBlobContent -Force -Context $Context -Container "auditlogs" -File $filePath -Blob $filename
Remove-PSSession -Id $Session.Id
How do I reference the application settings in Azure Function so that I can use the stored secrets in my program?
Please assist! Many thanks!
To access the app settings, keyvault or not, you must retrieve it trhough : $env:APPSETTING_YourSettingName
Thus, for your keyvault referenced secret, you would access it through the following variables.
$env:APPSETTING_SecretUserName
$env:APPSETTING_SecretPassword
$env:APPSETTING_SecretSAS
And if ever you need to produce a list of them.
Get-ChildItem env:APPSETTING_*
Note, the values returned will plain text unencrypted string.
Therefore, in your code, this:
$uSecret = (Get-ChildItem ENV:SecretUsername).SecretValueText
becomes that:
$uSecret = $env:APPSETTING_SecretUserName
Additional note
Since it was pointed out in the comments, I'll mention it.
I am not advocating the use of clear text secret in app settings at all.
App settings should be a keyvault referene for any sensitive data.
I am simply stating that it can be retrieved within the function at runtime as clear-text through the $env:APPSETTING_YourSettingName variable.
Example:
AppSetting name : MySecretUser
AppSetting value: #Microsoft.KeyVault(SecretUri=https://myvault.vault.azure.net/secrets/mysecret/ec96f02080254f109c51a1f14cdb1931)
Actual secret value (In the keyvault) : I_AM_Secret
At runtime, getting the value of $env:APPSETTING_MySecretUser will return a String Object with the value I_AM_Secret

powershell script to return all forwarding rules in org

I need to pull all forwarding rules for an exchange online environment, and output them to a csv. this sounds simple, but I have an additional caveat. there are 23,000 mailboxes in the org.
I was able to write the script I needed, it outputted the data, but it timed out.
then I was able to break out only certain mailboxes that were critical (11,000) but I was still timing out in powershell.
so finally, I found an article that detailed breaking up a script into blocks of 1,000, and running numerous sessions. and runs! it runs without timing out.
but it doesn't output to the csv anymore.
since my script has gone through several iterations, I'm pretty sure that my problem is the way I'm storing, or outputting the array, but for all my staring at this, I cant figure it out. short of asking the doc for a prescription of Adderall, I figured id ask here. below is the offending script.
the aliaslist.csv that it mentions is just a csv with a list of aliases for 11,000 mailboxes. if you would like to run your own tests, you can adjust $pagesize down and paste a few mailboxes into a csv called aliaslist, stored in c:\temp
Function New-O365ExchangeSession()
{
param(
[parameter(mandatory=$true)]
$365master)
#close any old remote session
Get-PSSession | Remove-PSSession -Confirm:$false
#start a new office 365 remote session
$365session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "https://outlook.office365.com/powershell-liveid/" -Credential $365master -Authentication Basic -AllowRedirection
$office365 = Import-PSSession $365session
}
#set input variables
$path = "C:\temp"
$InputFile = aliaslist.csv"
$UserEmail = "admin#domain.com"
#set variables for csv usage
$Offset = 0;
$PageSize = 1000;
$MbxMax = (Import-Csv "$path/$InputFile").count
#Loop in the list and retrieve the device’s information
$file = “c:\temp\office365-$((get-date).tostring(“yyyy-MM-dd”)).csv”
$365master = get-credential $UserEmail
New-O365ExchangeSession $365master
# call the office365 remote connection function
do{
$mbxlist=#(import-csv "$path/$InputFile"|select-object -skip $Offset -First $PageSize)
"Process entry $($Offset) to $($Offset+$PageSize)"
#end csv input count reference
ForEach($mbx in $MbxList)
{
#Write to Host
"start Processing $($mbx.alias)"
#end Write to host,
#Check rules
$rules = Get-InboxRule -mailbox $_.alias | ? {$_.RedirectTo -ne $null -or $_.ForwardTo -ne $null -or $_.ForwardAsAttachmentTo -ne $null}
If ($rules -ne $null)
{
$rules | % {
#check for forwardAsAttachments
If ($_.ForwardAsAttachmentTo -ne $null)
{
$obj = New-Object system.object
$obj | Add-Member -name "NetID" -Value $_.alias -MemberType NoteProperty
$obj | Add-Member -name "ForwardType" -Value "Forward As Attachment Rule" -MemberType NoteProperty
$obj | Add-Member -name "ForwardAddress" -Value $_.forwardAsAttachmentTo -MemberType NoteProperty
$obj | Add-Member -name "Enabled" -Value $_.Enabled -MemberType NoteProperty
$obj | Add-Member -name "Description" -Value $f -MemberType NoteProperty
If (Test-Path $file)
{
$mbx.alias + ”,” + ($obj | ConvertTo-Csv)[2] | Out-File $file –Append
}
Else
{
$obj | Export-Csv $file -Encoding ASCII -notypeinformation
}
}
$obj = $null
#check for redirects
If ($_.redirectto -ne $null)
{
$obj = New-Object system.object
$obj | Add-Member -name "NetID" -Value $_.alias -MemberType NoteProperty
$obj | Add-Member -name "ForwardType" -Value "Redirct Rule" -MemberType NoteProperty
$obj | Add-Member -name "ForwardAddress" -Value $_.redirectto -MemberType NoteProperty
$obj | Add-Member -name "Enabled" -Value $_.Enabled -MemberType NoteProperty
$obj | Add-Member -name "Description" -Value $c -MemberType NoteProperty
If (Test-Path $file)
{
$mbx.alias + ”,” + ($obj | ConvertTo-Csv)[2] | Out-File $file –Append
}
Else
{
$obj | Export-Csv $file -Encoding ASCII -notypeinformation
}
}
$obj = $null
#check for forwards
If ($_.ForwardTo -ne $null)
{
$obj = New-Object system.object
$obj | Add-Member -name "NetID" -Value $_.alias -MemberType NoteProperty
$obj | Add-Member -name "ForwardType" -Value "Forward Rule" -MemberType NoteProperty
$obj | Add-Member -name "ForwardAddress" -Value $_.forwardto -MemberType NoteProperty
$obj | Add-Member -name "Enabled" -Value $_.Enabled -MemberType NoteProperty
$obj | Add-Member -name "Description" -Value $f -MemberType NoteProperty
If (Test-Path $file)
{
($obj | ConvertTo-Csv)[2] | Out-File $file –Append
}
Else
{
$obj | Export-Csv $file -Encoding ASCII -notypeinformation
}
}
$obj = $null
}
}
}
#increment the start point for the next chunk
$Offset+=$PageSize
#Call the office365 remote session function to close the current one and open a new session
New-O365ExchangeSession $365master
} while($Offset -lt $MbxMax)

Add-Member in PowerShell Workflow

I have the following workflow in PowerShell:
workflow Audit-Computer
{
param([string[]]$Computers)
foreach -parallel ($computer in $Computers)
{
#Create a return object to hold the information we gather
$returnObject = [PSCustomObject]#{ComputerName=$computer}
#Test if the machine is reachable
$hostAlive = $true
try {
#Attempt a connection to the computer.
$os = Get-WmiObject –class Win32_OperatingSystem -PSComputerName $computer –erroraction Stop
#If we get here the connection was successful
$returnObject | Add-Member -MemberType NoteProperty -Name "HostAvailability" `
-Value "Online" -PassThru | `
Add-Member -MemberType NoteProperty -Name "OperatingSystem" -Value $os.Caption
} catch {
$hostAlive = $false
}
if($hostAlive){
#The host is alive lets run our audit
}
else
{
$returnObject | Add-Member -MemberType NoteProperty -Name "HostAvailability" -Value "Offline"
}
#Return the gathered information to the pipeline
Write-Output $returnObject
}
}
Unfortunately the Add-Member does not seem to work. The object returned does not contain the NoteProperties from add-member. How do I add properties to my custom object in a workflow?
There are several restrictions on cmdlets in PowerShell Workflows. According to this blog post, Add-Member is restricted to "local execution only".
I'm not able to test your script exactly, but this seemed to reproduce the same effect:
workflow Test-Workflow {
$obj = [PSCustomObject]#{ComputerName = 'test'}
$obj | Add-Member -MemberType NoteProperty -Name HostAvailable -Value "Online"
Write-Output $obj
}
but adding an InlineScript block made it work:
workflow Test-Workflow {
InlineScript {
$obj = [PSCustomObject]#{ComputerName = 'test'}
$obj | Add-Member -MemberType NoteProperty -Name HostAvailable -Value "Online"
Write-Output $obj
}
}

PowerShell to fetch installed programs

I will be hosting a file on a remote server (read-only) and asking people to run the file on their machines to gather installed program information. I want the file to be saved to their Desktop in their user space, so that I can then have them send it to us.
I have the script, but I'm not managing to obtain information from both "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", and "Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" in the same output file. I'm obviously missing something inherently obvious, as PowerShell is clearly able to do this, and I'm asking that someone please save me from my PEBKAC issue!
Thank you in advance, appreciated!
Here is my code;
$computers = "$env:computername"
$array = #()
foreach($pc in $computers){
$computername=$pc
$UninstallKey="SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall"
$UninstallKey="Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
$reg=[microsoft.win32.registrykey]::OpenRemoteBaseKey('LocalMachine',$computername)
$regkey=$reg.OpenSubKey($UninstallKey)
$subkeys=$regkey.GetSubKeyNames()
Write-Host "$computername"
foreach($key in $subkeys){
$thisKey=$UninstallKey+"\\"+$key
$thisSubKey=$reg.OpenSubKey($thisKey)
$obj = New-Object PSObject
$obj | Add-Member -MemberType NoteProperty -Name "ComputerName" -Value $computername
$obj | Add-Member -MemberType NoteProperty -Name "DisplayName" -Value $($thisSubKey.GetValue("DisplayName"))
$obj | Add-Member -MemberType NoteProperty -Name "DisplayVersion" -Value $($thisSubKey.GetValue("DisplayVersion"))
$obj | Add-Member -MemberType NoteProperty -Name "InstallLocation" -Value $($thisSubKey.GetValue("InstallLocation"))
$obj | Add-Member -MemberType NoteProperty -Name "Publisher" -Value $($thisSubKey.GetValue("Publisher"))
$array += $obj
}
}
$array | Where-Object { $_.DisplayName } | select ComputerName, DisplayName, DisplayVersion, Publisher | export-csv C:\Users\$env:username\Desktop\Installed_Apps.csv
Right now the following two lines set the same variable:
$UninstallKey="SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall"
$UninstallKey="Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
Use this:
$UninstallKey = #(
'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
'SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
)
Then wrap the real logic in:
$UninstallKey | ForEach-Object {
$regkey=$reg.OpenSubKey($_)
# the rest of your logic here
}