Using powershell to get the "Audit Policy" security setting value - powershell

I am trying to use Powershell (auditpol) to query the security setting values of the Audit Policy items. So far with all the auditpol commands, I only able to get the subcategories value instead.
auditpol /get /category:*
So far I could only get the list of the 9 items without the success/failure/no auditing values using:
auditpol /list/category
Could there be a command/flag that I might have left out for auditpol or is there any other command for me to retrieve the policies and its relevant security setting values?
Policy and values that I would like to query.

As you've found, auditpol only manages the settings that are in effect when the "Advanced Audit Policy Configuration" feature is enabled.
To query the "classic" audit policy, you will need to use the LSA Policy Win32 API to:
Open the local security policy using LsaOpenPolicy()
Query the audit settings using LsaQueryPolicyInformation()
Translate the results to something readable.
The following example uses Add-Type to compile a C# type that in turn does all of the above:
$AuditPolicyReader = Add-Type -TypeDefinition #'
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Linq;
using System.Collections.Generic;
public class AuditPolicyReader
{
[Flags()]
public enum AuditPolicySetting
{
Unknown = -1,
None = 0x0,
Success = 0x1,
Failure = 0x2
}
[StructLayout(LayoutKind.Sequential)]
private struct LSA_UNICODE_STRING
{
public UInt16 Length;
public UInt16 MaximumLength;
public IntPtr Buffer;
}
[StructLayout(LayoutKind.Sequential)]
private struct LSA_OBJECT_ATTRIBUTES
{
public int Length;
public IntPtr RootDirectory;
public LSA_UNICODE_STRING ObjectName;
public UInt32 Attributes;
public IntPtr SecurityDescriptor;
public IntPtr SecurityQualityOfService;
}
public struct POLICY_AUDIT_EVENTS_INFO
{
public bool AuditingMode;
public IntPtr EventAuditingOptions;
public Int32 MaximumAuditEventCount;
}
[DllImport("advapi32.dll")]
static extern uint LsaQueryInformationPolicy(IntPtr PolicyHandle, uint InformationClass, out IntPtr Buffer);
[DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
static extern uint LsaOpenPolicy(ref LSA_UNICODE_STRING SystemName, ref LSA_OBJECT_ATTRIBUTES ObjectAttributes, uint DesiredAccess, out IntPtr PolicyHandle);
[DllImport("advapi32.dll", SetLastError = true)]
static extern uint LsaClose(IntPtr ObjectHandle);
public static Dictionary<string, AuditPolicySetting> GetClassicAuditPolicy()
{
// Create dictionary to hold the audit policy settings (the key order here is important!!!)
var settings = new Dictionary<string, AuditPolicySetting>
{
{"System", AuditPolicySetting.Unknown},
{"Logon", AuditPolicySetting.Unknown},
{"Object Access", AuditPolicySetting.Unknown},
{"Privilige Use", AuditPolicySetting.Unknown},
{"Detailed Tracking", AuditPolicySetting.Unknown},
{"Policy Change", AuditPolicySetting.Unknown},
{"Account Management", AuditPolicySetting.Unknown},
{"Directory Service Access", AuditPolicySetting.Unknown},
{"Account Logon", AuditPolicySetting.Unknown},
};
// Open local machine security policy
IntPtr polHandle;
LSA_OBJECT_ATTRIBUTES aObjectAttributes = new LSA_OBJECT_ATTRIBUTES();
aObjectAttributes.Length = 0;
aObjectAttributes.RootDirectory = IntPtr.Zero;
aObjectAttributes.Attributes = 0;
aObjectAttributes.SecurityDescriptor = IntPtr.Zero;
aObjectAttributes.SecurityQualityOfService = IntPtr.Zero;
var systemName = new LSA_UNICODE_STRING();
uint desiredAccess = 2; // we only need the audit policy, no need to request anything else
var res = LsaOpenPolicy(ref systemName, ref aObjectAttributes, desiredAccess, out polHandle);
if (res != 0)
{
if(res == 0xC0000022)
{
// Access denied, needs to run as admin
throw new UnauthorizedAccessException("Failed to open LSA policy because of insufficient access rights");
}
throw new Exception(string.Format("Failed to open LSA policy with return code '0x{0:X8}'", res));
}
try
{
// now that we have a valid policy handle, we can query the settings of the audit policy
IntPtr outBuffer;
uint policyType = 2; // this will return information about the audit settings
res = LsaQueryInformationPolicy(polHandle, policyType, out outBuffer);
if (res != 0)
{
throw new Exception(string.Format("Failed to query LSA policy information with '0x{0:X8}'", res));
}
// copy the raw values returned by LsaQueryPolicyInformation() to a local array;
var auditEventsInfo = Marshal.PtrToStructure<POLICY_AUDIT_EVENTS_INFO>(outBuffer);
var values = new int[auditEventsInfo.MaximumAuditEventCount];
Marshal.Copy(auditEventsInfo.EventAuditingOptions, values, 0, auditEventsInfo.MaximumAuditEventCount);
// now we just need to translate the provided values into our settings dictionary
var categoryIndex = settings.Keys.ToArray();
for (int i = 0; i < values.Length; i++)
{
settings[categoryIndex[i]] = (AuditPolicySetting)values[i];
}
return settings;
}
finally
{
// remember to release policy handle
LsaClose(polHandle);
}
}
}
'# -PassThru |Where-Object Name -eq AuditPolicyReader
Now we can call GetClassicAuditPolicy() (remember to run this from an elevated prompt):
PS ~> $AuditPolicyReader::GetClassicAuditPolicy()
Key Value
--- -----
System None
Logon Success, Failure
Object Access None
Privilige Use None
Detailed Tracking None
Policy Change Success
Account Management Success, Failure
Directory Service Access None
Account Logon None

auditpol only returns the Advanced audit policy configuration. These settings can be found in the UI under Security Settings > Advanced Audit Policy Configuration > System Audit Policies
The legacy audit policy your screenshot shows were mostly done away with after Windows Server 2003/Windows Vista. Note the warnings in the policy properties or on the MS compatibility page:
For advanced policies, you can use /r to get a csv-formatted table:
auditpol /get /category:'Account Logon' /r | ConvertFrom-Csv |
Format-Table 'Policy Target',Subcategory,'Inclusion Setting'
Policy Target Subcategory Inclusion Setting
------------- ----------- -----------------
System Kerberos Service Ticket Operations No Auditing
System Other Account Logon Events No Auditing
System Kerberos Authentication Service No Auditing
System Credential Validation No Auditing
For legacy audit policies:
secedit.exe /export /areas SECURITYPOLICY /cfg filename.txt
[Event Audit]
AuditSystemEvents = 0
AuditLogonEvents = 0
AuditObjectAccess = 0
AuditPrivilegeUse = 0
AuditPolicyChange = 0
AuditAccountManage = 0
AuditProcessTracking = 0
AuditDSAccess = 0
AuditAccountLogon = 0
Requires that it hasn't been disabled. Check in the registry:
Get-ItemProperty HKLM:\System\CurrentControlSet\Control\Lsa -Name SCENoApplyLegacyAuditPolicy

Here is a code that gives you a list of all categories and subcategories with their current audit-status. I made it a bit longer than really needed to add the local names of each object. Also see some usage-samples at the end of the code.
# getting the audit policy settings for each subcategory
# works for any OS language
cls
Remove-Variable * -ea 0
$ErrorActionPreference = 'stop'
#requires -runasadmin
$dll = [string]::Join("`r`n", '[DllImport("advapi32.dll")]', 'public static extern bool')
$auditpol = Add-Type -Name 'AuditPol' -Namespace 'Win32' -PassThru -MemberDefinition "
$dll AuditEnumerateCategories(out IntPtr catList, out uint count);
$dll AuditLookupCategoryName(Guid catGuid, out string catName);
$dll AuditEnumerateSubCategories(Guid catGuid, bool all, out IntPtr subList, out uint count);
$dll AuditLookupSubCategoryName(Guid subGuid, out String subName);
$dll AuditQuerySystemPolicy(Guid subGuid, uint count, out IntPtr policy);
$dll AuditFree(IntPtr buffer);"
Add-Type -TypeDefinition "
using System;
public struct AUDIT_POLICY_INFORMATION {
public Guid AuditSubCategoryGuid;
public UInt32 AuditingInformation;
public Guid AuditCategoryGuid;
}"
function getPolicyInfo($sub) {
# get policy info for one subcategory:
$pol = new-object AUDIT_POLICY_INFORMATION
$size = $ms::SizeOf($pol)
$ptr = $ms::AllocHGlobal($size)
$null = $ms::StructureToPtr($pol, $ptr, $false)
$null = $auditpol::AuditQuerySystemPolicy($sub, 1, [ref]$ptr)
$pol = $ms::PtrToStructure($ptr, [type][AUDIT_POLICY_INFORMATION])
$null = $ms::FreeHGlobal($ptr)
[PsCustomObject]#{
category = $pol.AuditCategoryGuid
success = [bool]($pol.AuditingInformation -band 1)
failure = [bool]($pol.AuditingInformation -band 2)
}
}
# (optional) get GUID and local name of all categories:
$ms = [System.Runtime.InteropServices.Marshal]
$count = [uint32]0
$buffer = [IntPtr]::Zero
$size = $ms::SizeOf([type][guid])
$null = $auditpol::AuditEnumerateCategories([ref]$buffer,[ref]$count)
$ptr = [int64]$buffer
$name = [System.Text.StringBuilder]::new()
$catList = #{}
foreach($id in 1..$count) {
$guid = $ms::PtrToStructure([IntPtr]$ptr,[type][guid])
$null = $auditpol::AuditLookupCategoryName($guid,[ref]$name)
$catList[$guid] = $name
$ptr += $size
}
$null = $auditpol::AuditFree($buffer)
# get all subcategories (with optional name):
$guid = [guid]::Empty
$null = $auditpol::AuditEnumerateSubCategories($guid, $true, [ref]$buffer, [ref]$count)
$ptr = [int64]$buffer
$subList = #{}
foreach($id in 1..$count) {
$guid = $ms::PtrToStructure([IntPtr]$ptr,[type][guid])
$null = $auditpol::AuditLookupSubCategoryName($guid,[ref]$name)
$pol = getPolicyInfo $guid
$data = [psCustomObject]#{
category = $catList[$pol.category]
subcategory = $name
success = $pol.success
failure = $pol.failure
}
$subList[$guid.guid] = $data
$ptr += $size
}
$null = $auditpol::AuditFree($buffer)
# listing all subCategories and their audit settings:
$subList.Values | sort category, subcategory | ft -AutoSize
# gettings the audit-settings for a given subcategory-GUID (without '{}'):
$process_creation_guid = '0CCE922B-69AE-11D9-BED3-505054503030'
$subList[$process_creation_guid]

Related

Powershell interaction between SYSTEM user and logged on users

I want a powershell script running as SYSTEM user to display a Windowsform on another users session and have interaction with the controls of it.
I am trying to automate the installation/repair of Symantec Endpoint Protection with Solarwinds N-Able. This platform uses agent software which is installed on clients to monitor and execute tasks on them.
The agent uses the NT AUTHORITY\SYSTEM user to execute tasks on the machine. The installation of SEP works fine so far, but the reboots in between the deinstall/install phases are still uncontrollable as a regular user on the machine. I want the currently active user be able to control this reboot cycles. Something like the Windows update reboot prompt.
My idea is to display a windowsform on logged on user's desktop with controls on it to execute or delay the reboot. My question now is how do I display a windowsform defined in powershell on another user's session, and how am I going to get the actions of the controls back in the script that is running on the SYSTEM user.
I've already tried the msg command to send a message to all the users on the system. But this is only one-way communication and isn't really meant to be used in situations like this is guess.
I found the solution for my problem. I used the WTSSendMessage function which boxdog suggested in the comments. I combined this with a script that gets the sessionID's of the logged on users. I minimized this script to only get the "Active" user's sessionID. This is then used to send the message to user. I tested it in Solarwinds and so far this works flawless.
My coding skills are pretty basic, but this is the end result.
function Send-MessageBox
{
[CmdletBinding()]
[OutputType([string])]
Param
(
[Parameter(Mandatory=$true, Position=0)]
[string]$title,
[Parameter(Mandatory=$true, Position=1)]
[string]$message,
[Parameter(Mandatory=$true, Position=2)]
[int]$duration,
[Parameter(Mandatory=$true, Position=3)]
[int]$style
)
Begin
{
$typeDefinition = #"
using System;
using System.Runtime.InteropServices;
public class WTSMessage {
[DllImport("wtsapi32.dll", SetLastError = true)]
public static extern bool WTSSendMessage(
IntPtr hServer,
[MarshalAs(UnmanagedType.I4)] int SessionId,
String pTitle,
[MarshalAs(UnmanagedType.U4)] int TitleLength,
String pMessage,
[MarshalAs(UnmanagedType.U4)] int MessageLength,
[MarshalAs(UnmanagedType.U4)] int Style,
[MarshalAs(UnmanagedType.U4)] int Timeout,
[MarshalAs(UnmanagedType.U4)] out int pResponse,
bool bWait
);
static int response = 0;
public static int SendMessage(int SessionID, String Title, String Message, int Timeout, int MessageBoxType) {
WTSSendMessage(IntPtr.Zero, SessionID, Title, Title.Length, Message, Message.Length, MessageBoxType, Timeout, out response, true);
return response;
}
}
"#
}
Process
{
if (-not ([System.Management.Automation.PSTypeName]'WTSMessage').Type)
{
Add-Type -TypeDefinition $typeDefinition
}
$RawOuput = (quser) -replace '\s{2,}', ',' | ConvertFrom-Csv
$sessionID = $null
Foreach ($session in $RawOuput) {
if(($session.sessionname -notlike "console") -AND ($session.sessionname -notlike "rdp-tcp*")) {
if($session.ID -eq "Active"){
$sessionID = $session.SESSIONNAME
}
}else{
if($session.STATE -eq "Active"){
$sessionID = $session.ID
}
}
}
$response = [WTSMessage]::SendMessage($sessionID, $title, $message, $duration, $style )
}
End
{
Return $response
}
}
Send-MessageBox -title "Title" -message "Message" -duration 60 -style 0x00001034L

How to check if HTA is foreground window?

I'm trying to determine if an HTA is the foreground window. The following PowerShell will normally identify the foreground window:
Add-Type #"
using System;
using System.Runtime.InteropServices;
public class UserWindows {
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
}
"#
$a = [UserWindows]::GetForegroundWindow()
get-process | ? { $_.mainwindowhandle -eq $a }
But, if the HTA is in the foreground, no process is returned. (I.E. there is a MainWindowHandle, but there is no process!?)
The MSHTA process has an entirely different MainWindowHandle, but no window.
Process Hacker identifies the HTA window (frame or contents) as the mshta process.
How can I match up the HTA window and mshta.exe via script? Alternatively, how can I get the MainWindowHandle of the HTA window without knowing if it is in front?
The Foregroundwindow returned is not a mainwindowhandle of any process but just a window handle of mshta, you'll have to check all window handles.
I did manually with the tool cmdow.exe (had to convert the handle to hex) and got this for my example The HTA helpomatic :
> cmdow 0x14E0F46
Handle Lev Pid -Window status- Image Caption
0x14E0F46 1 153048 Res Ina Ena Vis mshta The HTA Helpomatic -- Presented by t
There should be better/more PowerShellish ways to enumerate the window handles but this changed script will use the mentioned cmdow.exe
## Get-ForegrounWindow.ps1
Add-Type #"
using System;
using System.Runtime.InteropServices;
public class UserWindows {
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
}
"#
while ($true -eq 'true') {
$ForeGroundWin = [UserWindows]::GetForegroundWindow()
$handle = "0x{0:x}" -f $($ForeGroundWin.ToInt64())
cmdow.exe $Handle /B /F
Sleep -sec 5
}
Sample output:
> .\Get-ForegrounWindow.ps1
0x15C04A2 1 127148 Res Act Ena Vis powershell Windows PowerShell
0x7F0DE4 1 135416 Res Act Ena Vis TextPad TextPad - Q:\Test\2017-06\09\Get-ForegrounWindow.ps1
0x16205D0 1 121732 Res Act Ena Vis bash usernamet#computer: ~
0x14E0F46 1 153048 Res Act Ena Vis mshta The HTA Helpomatic -- Presented by the Microsoft Scripting Guys
Modified the answer from here to get this which does what I need:
Add-Type #"
using System;
using System.Runtime.InteropServices;
using System.Text;
public class UserWindows {
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int GetWindowText(IntPtr hwnd,StringBuilder lpString, int cch);
[DllImport("user32.dll", SetLastError=true, CharSet=CharSet.Auto)]
public static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll", SetLastError=true, CharSet=CharSet.Auto)]
public static extern Int32 GetWindowTextLength(IntPtr hWnd);
}
"#
while(1) {
$ForgroundWindow = [UserWindows]::GetForegroundWindow()
$FGWTitleLength = [UserWindows]::GetWindowTextLength($ForgroundWindow)
$StringBuilder = New-Object text.stringbuilder -ArgumentList ($FGWTitleLength + 1)
$null = [UserWindows]::GetWindowText($ForgroundWindow,$StringBuilder,$StringBuilder.Capacity)
if ($StringBuilder.ToString() -match $HTAWindowTitleRegEx) {
# Put further scripting here for when the HTA window is in front
}
Start-Sleep -Seconds 1
}
Hope that helps someone.

Machine level environment variable not available after setting in Powershell

The following code does not return "Y" as expected. Only in the next session (another new window) it works? I would expect it to be available immediately?
[Environment]::SetEnvironmentVariable("X", "Y", "Machine")
Write-Host $env:X
You must do this since the process gets env vars on start, not while running (i.e. you would have to restart shell for this to work your way):
[Environment]::SetEnvironmentVariable("X", "Y", "Machine")
$Env:X = "Y"
There is also a way to broadcast this to other windows using WM_SETTINGCHANGE
To effect a change in the environment variables for the system or the
user, broadcast this message with lParam set to the string
"Environment".)
# Notify system of change via WM_SETTINGCHANGE
if (! ("Win32.NativeMethods" -as [Type]))
{
Add-Type -Namespace Win32 -Name NativeMethods -MemberDefinition #"
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr SendMessageTimeout( IntPtr hWnd, uint Msg, UIntPtr wParam, string lParam, uint fuFlags, uint uTimeout, out UIntPtr lpdwResult);
"#
}
$HWND_BROADCAST = [IntPtr] 0xffff; $WM_SETTINGCHANGE = 0x1a; $result = [UIntPtr]::Zero
[Win32.Nativemethods]::SendMessageTimeout($HWND_BROADCAST, $WM_SETTINGCHANGE, [UIntPtr]::Zero, "Environment", 2, 5000, [ref] $result) | out-null
}
As far as I know, a process loads the environment variables only once (at start). But you can change it using:
[Environment]::SetEnvironmentVariable("X", "Y", "Process") # for the current session
Note: You probably want to set both:
[Environment]::SetEnvironmentVariable("X", "Y", "Machine")
[Environment]::SetEnvironmentVariable("X", "Y", "Process")

PowerShell error uploading blob text to Azure: UploadText(string)

I have a powershell module which attempts to upload a blob to azure storage. Everything checks out until the last line which actually uploads the blob.
I receive the following error:
Exception calling "UploadText" with "1" argument(s):
"The specified resource does not exist."
At line:1 char:1
+ $blob.UploadText("asdasdfsdf")
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : StorageClientException
I have also tried using the overload with 3 args, but the same issue exists there as well.
Here is the module:
Function Add-BlobText
{
[CmdletBinding()]
param(
[Parameter(Mandatory = $true,Position = 0)]
[string]
$StorageAccount,
[Parameter(Mandatory = $true,Position = 1)]
[string]
$Container,
[Parameter(Mandatory = $true,Position = 2)]
[string]
$BlobName,
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[string]
$BlobText
) #end param
Add-Type -Path "C:\Assemblies\Microsoft.WindowsAzure.StorageClient.dll"
Set-AzureSubscription -SubscriptionName "MySubName"
$secondaryKey = (Get-AzureStorageKey -StorageAccountName $storageAccount).Secondary
$creds = New-Object Microsoft.WindowsAzure.StorageCredentialsAccountAndKey($StorageAccount,$secondaryKey)
$cloudStorageAccount = New-Object Microsoft.WindowsAzure.CloudStorageAccount($creds, $true)
[Microsoft.WindowsAzure.StorageClient.CloudBlobClient]$cloudBlobClient = New-Object Microsoft.WindowsAzure.StorageClient.CloudBlobClient($cloudStorageAccount.BlobEndpoint)
[Microsoft.WindowsAzure.StorageClient.CloudBlobContainer]$blobContainer = $cloudBlobClient.GetContainerReference($Container)
[Microsoft.WindowsAzure.StorageClient.CloudBlob]$blob = $blobContainer.GetBlobReference($BlobName)
$blob.UploadText($BlobText)
} #end Function Add-BlobText
Update:
I have been able to get this working as a binary module (below). If anyone can figure out why UploadText() works within a binary module but throws an exception in a script module, please let me know.
[Cmdlet(VerbsCommon.Add, "BlobText")]
public class AddBlobText : PSCmdlet
{
[Parameter(Mandatory = true, Position = 0)]
public string StorageAccount { get; set; }
[Parameter(Mandatory = true, Position = 1)]
public string Container { get; set; }
[Parameter(Mandatory = true, Position = 2)]
public string BlobName { get; set; }
[Parameter(Mandatory = true, ValueFromPipeline = true)]
public string BlobText { get; set; }
protected override void ProcessRecord()
{
PowerShell ps = PowerShell.Create();
ps.AddScript("Set-AzureSubscription -SubscriptionName 'MySubName'");
string keyScript = "( Get-AzureStorageKey -StorageAccountName " + StorageAccount + " ).Secondary";
ps.AddScript(keyScript);
Collection<PSObject> result = ps.Invoke();
string secondaryKey = result[0].ToString();
StorageCredentialsAccountAndKey credentials = new StorageCredentialsAccountAndKey(StorageAccount, secondaryKey);
CloudStorageAccount storageAccount = new CloudStorageAccount(credentials, true);
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
CloudBlobContainer container = blobClient.GetContainerReference(Container);
var blob = container.GetBlobReference(BlobName);
blob.UploadText(BlobText);
}
}
This is probably because your container does not exist. You should call CreateIfNotExist after initializing the container to make sure it exists:
[Microsoft.WindowsAzure.StorageClient.CloudBlobContainer]$blobContainer = $cloudBlobClient.GetContainerReference($Container)
$blobContainer.CreateIfNotExist() <-- Here
[Microsoft.WindowsAzure.StorageClient.CloudBlob]$blob = $blobContainer.GetBlobReference($BlobName)
$blob.UploadText($BlobText)
This error is very ambiguous and misleading but there are instances' where Azure Storage can get "confused". Looking at Sandrino's example and specifically this line,
[Microsoft.WindowsAzure.StorageClient.CloudBlob]$blob = $blobContainer.GetBlobReference($BlobName)
Not that Sandrino's answer is your issue but the exception you encountered will happen when passing a Url or possibly other confusing key strings to Azure Storage Containers.
Unfortunately I am not a Powershell guy but here is a reproducing example then fix in C#.
public void Save(string objId, T obj)
{
CloudBlob blob = this.container.GetBlobReference(objId); // Problematic if a URL
blob.Properties.ContentType = "application/json";
var serialized = string.Empty;
serialized = serializer.Serialize(obj);
if (this.jsonpSupport)
{
serialized = this.container.Name + "Callback(" + serialized + ")";
}
blob.UploadText(serialized);
}
Assume that this.container is a valid blob storage instance pointing to http://127.0.0.1:10000/devstoreaccount1/sggames or whatever you have for a valid container.
And objId is a key that contains a Url like https://www.google.com/accounts/o8/id?id=AItOawk4Dw9sLxSc-zmdWQHdZNcyzkTcvKUkhiE ...and yes this can happen, in my case this is an actual identity claim from Google using Azure ACS.
After the GetBlobReference call the blob instance has become corrupt which now looks at a messed up Uri -> https://www.google.com/accounts/o8/id?id=AItOawk4Dw9sLxSc-zmdWQHdZNcyzkTcvKUkhiE
Unfortunately the solution to simply call $blobContainer.CreateIfNotExist() is not a solution and wouldn't work. Key's that contain a Uri structure will simply be used to re-interpret the blob storage location.
The work around (other than daredev's Update) would be something like this:
if (Uri.IsWellFormedUriString(claim, UriKind.Absolute) && HttpUtility.ParseQueryString(claim).Count > 0)
{
claim = HttpUtility.ParseQueryString(claim)[0];
}
Add this code within my method above to clean up any Uri's, but you could use any appropriate method like Base64 encoding URLs if you need to maintain the full key.
Here are the before and after images showing the results as I described.
The bad:
notice the bad URI
this bad URI munged up the actual storage blob location
here is the same exception daredev had
The good:
the new scrubbed key, notice it's just the value on the URL's query string
Azure Storage URI looks good now
Eureka!
Hope this helps.
This is the PowerShell script I use to upload a file to Azure Blob: Uploading to Azure Storage
$SubscriptionName = ""
$SubscriptionId = ""
$DestContainer = ""
$StorageAccountName = ""
Import-AzurePublishSettingsFile -PublishSettingsFile "<Location of the publishsettings-file>"
Set-AzureSubscription -SubscriptionId $SubscriptionId -CurrentStorageAccountName $StorageAccountName
Select-AzureSubscription -SubscriptionName $SubscriptionName
Set-AzureStorageBlobContent -File "<File you want to upload>" -Container $DestContainer

Powershell script from shortcut to change desktop

Any ideas and suggestions on why this works when run from w/in PS, but not when run from a shortcut defined as:
%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe -File "C:\Users\bin\ChangeDesktop.ps1"
Contents of ChangeDesktop.ps1:
set-itemproperty -path "HKCU:Control Panel\Desktop" -name WallPaper -value ""
rundll32.exe user32.dll, UpdatePerUserSystemParameters
If I am in the PS "command prompt" environment the desktop background is automatically removed and refreshed, outside of that I have to manually refresh the desktop to effect the change.
System is Windows Server 2008 R2 - fresh install. Script executionpolicy is set to RemoteSigned, and I don't see any PS errors. I Just don't see the desktop refresh automatically when running from a desktop shortcut.
scratches head
rundll32.exe user32.dll, UpdatePerUserSystemParameters didn't actually change the wallpaper for me on a 2008 x64 box. This does did though... It calls the Win32 API to invoke changing the wallpaper. If you save this as your ChangeDesktop.ps1 script it should work. As it is below it will remove any desktop wallpaper. However if you do want to set one you can edit the last line with the path of a supported image file like this:
[Wallpaper.Setter]::SetWallpaper( 'C:\Wallpaper.bmp', 0 )
The second argument is for the styling:
0: Tile
1: Center
2: Stretch
3: No Change
The script:
Add-Type #"
using System;
using System.Runtime.InteropServices;
using Microsoft.Win32;
namespace Wallpaper
{
public enum Style : int
{
Tile, Center, Stretch, NoChange
}
public class Setter {
public const int SetDesktopWallpaper = 20;
public const int UpdateIniFile = 0x01;
public const int SendWinIniChange = 0x02;
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern int SystemParametersInfo (int uAction, int uParam, string lpvParam, int fuWinIni);
public static void SetWallpaper ( string path, Wallpaper.Style style ) {
SystemParametersInfo( SetDesktopWallpaper, 0, path, UpdateIniFile | SendWinIniChange );
RegistryKey key = Registry.CurrentUser.OpenSubKey("Control Panel\\Desktop", true);
switch( style )
{
case Style.Stretch :
key.SetValue(#"WallpaperStyle", "2") ;
key.SetValue(#"TileWallpaper", "0") ;
break;
case Style.Center :
key.SetValue(#"WallpaperStyle", "1") ;
key.SetValue(#"TileWallpaper", "0") ;
break;
case Style.Tile :
key.SetValue(#"WallpaperStyle", "1") ;
key.SetValue(#"TileWallpaper", "1") ;
break;
case Style.NoChange :
break;
}
key.Close();
}
}
}
"#
[Wallpaper.Setter]::SetWallpaper( '', 0 )
Originally from PoshCode: http://poshcode.org/491
This might sounf weird, but what worked for me was using single quotes instead of double. So it would look like this:
Set-ItemProperty -path "HKCU:Control Panel\Desktop" -name 'wallpaper' -value 'some value'
rundll32.exe user32.dll, UpdatePerUserSystemParameters
This script works wonders. For a domain deployment, we didn't want it to continuously change the background each time a user logs in.
I made the following changes so that it checks to see if the background exists on the computer in the desired location, if it does exist then to exit, if it does not to go ahead with the file copy and set the background.
it first maps the hidden share, copies the file to the desired directory, sets the wallpaper and then disconnects the hidden share. if "X" is already used by your company insert another drive letter. :D
$strFileName="C:\Users\Public\Pictures\background.jpg"
If (Test-Path $strFileName){
# // File exists
Exit-PSSession
}Else{
# // File does not exist
New-PSDrive -Name X -PSProvider Filesystem -Root \\hiddenfileshare\wallpapers
Copy-Item X:\background.jpg C:\Users\Public\Pictures
[Wallpaper.Setter]::SetWallpaper( 'C:\Users\Public\Pictures\background.jpg', 0 )
Remove-PSDrive X
}
The script provided by Andy Arismendi is awesome!
I've used it to make a fun project - set a random wallpaper scraped from the net.
I'm posting it here for anyone interested. Before using it you need to change a few constants at the top of the script source. You also need to download the HtmlAgilityPack.dll library (there are instructions in the script comments).
Enjoy!
P.S. If the wallpaper site I'm using goes down or changes its layout the scraping in the script will go to hell, but nevertheless with my script as an example I bet you'll be able to build another wallpaper scraper.
############## CONSTANTS ##############
# add the library for parsing html - HtmlAgilityPack - download it with nuget from https://www.nuget.org/packages/HtmlAgilityPack
# download nuget command line from https://dist.nuget.org/index.html and install HtmlAgilityPack with "nuget install HtmlAgilityPack" from the cmd
# enter the path to HtmlAgilityPack.dll library used for html parsing
$html_parser_path = "C:\Users\username\Documents\htmlagilitypack\HtmlAgilityPack.1.4.9.5\lib\Net20\HtmlAgilityPack.dll"
# choose where your wallpapers will be downloaded
$wallpaper_dir_path = "C:\Users\username\Pictures\"
# get random wallpaper category from wallpaperscraft.com - the ones below are my favourite categories, edit it if you want to get other categories
<#
you can choose your favorite wallpaper categories from the list below
3D
Abstract
Animals
Anime
Brands
Cars
City
Fantasy
Flowers
Food
Games
Girls
Hi-Tech
Holidays
Macro
Men
Movies
Music
Nature
Other
Space
Sport
Textures
TV Series
Vector
#>
$categories = #("animals","flowers","macro","nature","space")
# I download my wallpapers from the site below - real quality wallpapers
# don't forget to change your resolution - I'm using a 1920x1080 monitor
<#
A list of resolutions to choose from:
1600x1200
1400x1050
1280x1024
1280x960
1152x864
1024x768
3840x2400
3840x2160
3840x1200
2560x1600
2560x1440
2560x1080
2560x1024
2048x1152
1920x1200
1920x1080
1680x1050
1600x900
1440x900
1280x800
1280x720
2160x3840
1440x2560
1366x768
1080x1920
1024x600
960x544
800x1280
800x600
720x1280
540x960
480x854
480x800
400x480
360x640
320x480
320x240
240x400
240x320
2732x2732
2048x2048
1080x1920
1024x1024
750x1334
640x1136
640x960
320x480
1366x768
1920x1080
360x640
1024x768
1600x900
1280x900
1440x900
1280x1024
800x600
1680x1050
2560x1440
320x480
1920x1200
480x800
720x1280
#>
$resolution = "1920x1080" # default resolution
$url = "https://wallpaperscraft.com/catalog/category/$resolution" # category is a placeholder
############## END OF CONSTANT DECLARATIONS ##############
Add-Type #"
using System;
using System.Runtime.InteropServices;
using Microsoft.Win32;
namespace Wallpaper
{
public enum Style : int
{
Tile, Center, Stretch, NoChange
}
public class Setter {
public const int SetDesktopWallpaper = 20;
public const int UpdateIniFile = 0x01;
public const int SendWinIniChange = 0x02;
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern int SystemParametersInfo (int uAction, int uParam, string lpvParam, int fuWinIni);
public static void SetWallpaper ( string path, Wallpaper.Style style ) {
SystemParametersInfo( SetDesktopWallpaper, 0, path, UpdateIniFile | SendWinIniChange );
RegistryKey key = Registry.CurrentUser.OpenSubKey("Control Panel\\Desktop", true);
switch( style )
{
case Style.Stretch :
key.SetValue(#"WallpaperStyle", "2") ;
key.SetValue(#"TileWallpaper", "0") ;
break;
case Style.Center :
key.SetValue(#"WallpaperStyle", "1") ;
key.SetValue(#"TileWallpaper", "0") ;
break;
case Style.Tile :
key.SetValue(#"WallpaperStyle", "1") ;
key.SetValue(#"TileWallpaper", "1") ;
break;
case Style.NoChange :
break;
}
key.Close();
}
}
}
"#
Add-Type -Path $html_parser_path
$rand_index = Get-Random -minimum 0 -maximum $categories.Length
$random_category = $categories[$rand_index]
# replace the placeholder "category" with the random category chosen above
$url = $url -replace "category", $random_category
$doc = New-Object HtmlAgilityPack.HtmlDocument
$doc.LoadHtml((New-Object System.Net.WebClient).DownloadString($url))
# NOTE: the html parser I'm using locates elements by XPath only
$page_links = $doc.DocumentNode.SelectSingleNode("//div[contains(#class, 'pages')]").SelectNodes("a")
# get last page link
$last_page_link = $page_links[$page_links.Count - 1].GetAttributeValue("href", "")
# get last page number
$last_page_number = [regex]::match($last_page_link,'.*page(\d+)').Groups[1].Value
$random_page_number = Get-Random -minimum 0 -maximum $last_page_number
$random_page_addr = ""
# page 1 doesn't add anything to the url
if ($random_page_number -gt 0){
$random_page_addr = "/page$random_page_number"
}
$doc.LoadHtml((New-Object System.Net.WebClient).DownloadString("$url$random_page_addr"))
# get wallpaper divs
$wallpaper_divs = $doc.DocumentNode.SelectNodes("//div[contains(#class, 'wallpaper_pre')]")
$random_wallpaper_div = Get-Random -minimum 0 -maximum 15 # there are 15 wallpapers on a page
# get a sample wallpaper link which has to be substituted later
$sample_wallpaper_link = $wallpaper_divs[$random_wallpaper_div].SelectNodes("a")[0].GetAttributeValue("href", "")
# substitute the above link to get the image link itself
$sample_wallpaper_link = $sample_wallpaper_link -replace "download", "image"
$sample_wallpaper_link = $sample_wallpaper_link -replace "/$resolution", "_$resolution.jpg"
$sample_wallpaper_link = $sample_wallpaper_link -replace "//", "https://"
$wallpaper_image_name = [regex]::match($sample_wallpaper_link,'.*image/(\w+)').Groups[1].Value
$wallpaper_image_name = "$wallpaper_image_name.jpg"
$wc = New-Object System.Net.WebClient
$save_location = "$wallpaper_dir_path$wallpaper_image_name"
$wc.DownloadFile($sample_wallpaper_link, "$save_location")
[Wallpaper.Setter]::SetWallpaper($save_location, 1 )