TetheringWiFiBand in Windows.Networking.NetworkOperators - powershell

I was trying to use Powershell script to set up windows 10 mobile hotspot (which is the function in "Settings" -> "Network & Internet" -> "Mobile hotspot"). I was able to turn it on through the following script:
[Windows.System.UserProfile.LockScreen,Windows.System.UserProfile,ContentType=WindowsRuntime] | Out-Null
# Define functions. Not important to this question
Add-Type -AssemblyName System.Runtime.WindowsRuntime
$asTaskGeneric = ([System.WindowsRuntimeSystemExtensions].GetMethods() | ? { $_.Name -eq 'AsTask' -and $_.GetParameters().Count -eq 1 -and $_.GetParameters()[0].ParameterType.Name -eq 'IAsyncOperation`1' })[0]
Function Await($WinRtTask, $ResultType) {
$asTask = $asTaskGeneric.MakeGenericMethod($ResultType)
$netTask = $asTask.Invoke($null, #($WinRtTask))
$netTask.Wait(-1) | Out-Null
$netTask.Result
}
Function AwaitAction($WinRtAction) {
$asTask = ([System.WindowsRuntimeSystemExtensions].GetMethods() | ? { $_.Name -eq 'AsTask' -and $_.GetParameters().Count -eq 1 -and !$_.IsGenericMethod })[0]
$netTask = $asTask.Invoke($null, #($WinRtAction))
$netTask.Wait(-1) | Out-Null
}
# Create tethering manager
$connectionProfile = [Windows.Networking.Connectivity.NetworkInformation,Windows.Networking.Connectivity,ContentType=WindowsRuntime]::GetInternetConnectionProfile()
$tetheringManager = [Windows.Networking.NetworkOperators.NetworkOperatorTetheringManager,Windows.Networking.NetworkOperators,ContentType=WindowsRuntime]::CreateFromConnectionProfile($connectionProfile)
# Create configuration
$configuration = new-object Windows.Networking.NetworkOperators.NetworkOperatorTetheringAccessPointConfiguration
$configuration.Ssid = "test"
$configuration.Passphrase = "12345678"
# ===================== My question is here =====================
[enum]::GetValues([Windows.Networking.NetworkOperators.TetheringWiFiBand])
$configuration | Get-Member
# ===============================================================
# Check whether Mobile Hotspot is enabled
$tetheringManager.TetheringOperationalState
# Set Hotspot configuration
AwaitAction ($tetheringManager.ConfigureAccessPointAsync($configuration))
# Start Mobile Hotspot
Await ($tetheringManager.StartTetheringAsync()) ([Windows.Networking.NetworkOperators.NetworkOperatorTetheringOperationResult])
but I could only set the SSID and the Passphrase of the network but not the network band which we can choose between '5 GHz', '2.4 GHz' or 'Any available' in the setting gui.
I saw on this post (https://blogs.windows.com/windowsdeveloper/2019/09/10/windows-10-sdk-preview-build-18975-available-now/#DwOj8B0wPu5zd9hK.97) that it seems there is an enum 'TetheringWiFiBand' to set that starting from Windows 10 SDK, version 1903, build 18362 (it's exactly my windows version). However, as you can see, in the middle of my script, when I tried to access this enum, I got an error:
Unable to find type [Windows.Networking.NetworkOperators.TetheringWiFiBand].
Also, there is no such an enum when I print the member of Windows.Networking.NetworkOperators.NetworkOperatorTetheringAccessPointConfiguration
Does anyone have an idea of how to set the wifi band of the mobile hotspot based on this method? Thanks.

For anyone who might be interested, the property of setting the tethering band has been added in WinRT Build 19041
(See: https://learn.microsoft.com/en-us/uwp/api/windows.networking.networkoperators.networkoperatortetheringaccesspointconfiguration?view=winrt-19041
and
https://learn.microsoft.com/en-us/uwp/api/windows.networking.networkoperators.tetheringwifiband?view=winrt-19041)
So now you can add a line in the script posted in the question:
$configuration.Band = 1
to specify the tethering band to 2.4 GHz.

Related

All but one of my elseif loops is running - what is wrong with my syntax?

I am writing a script that checks for a registry key property created by a Bitlocker GPO, checks to see if the device is already protected and if the key is present and the device is not protected then encrypts the device.
I've tested each of these functions individually and they all work, and they all seem to work in the context of the script, outputting the right loop, however when I run the whole script at once, the only loop that DOESN'T work is the loop where the encrypting function runs.
I have the exit codes here because I am using Manage Engine to push out the script to a few remote devices around our estate and I would like to be able to see easily where the script is failing so I can deploy something else.
# FUNCTION - Function created to check existence of Active Directory GPO
Function Test-RegistryValue {
$regPath = 'HKLM:\SOFTWARE\Policies\Microsoft\FVE'
$regPropertyName = 'ActiveDirectoryBackup'
# Value of registry key ported into a script-level variable accessible from outside the function
$regValue = (Get-ItemPropertyValue -Path $regPath -Name $regPropertyName -ErrorAction ignore)
if ($regValue -eq 1)
{
$script:regExit = 0
}
# If the key exists but is not set to write back
elseif ($regValue -eq 0)
{
$script:regExit = 1
}
# If the registry property doesn't exist
else
{
$script:regExit = 5
}
}
# FUNCTION - Function tests if BitLocker protection is already on
function Test-TBBitlockerStatus {$BLinfo = Get-Bitlockervolume
if($blinfo.ProtectionStatus -eq 'On' -and $blinfo.EncryptionPercentage -eq '100')
{
return $true
}
else
{
return $false
}
}
# FUNCTION - This function is the actual encryption script
Function Enable-TBBitlocker {
Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -RecoveryPasswordProtector
$RecoveryKey = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector | Where-Object {$_.KeyProtectorType -eq "RecoveryPassword"}
Manage-bde -protectors -adbackup $env:SystemDrive -id $RecoveryKey[0].KeyProtectorId
Enable-BitLocker -MountPoint $env:SystemDrive -UsedSpaceOnly -TpmProtector -SkipHardwareTest
}
# MAIN SCRIPT
# Running the GPO check function
Test-RegistryValue
Test-TBBitlockerStatus
$regExit
if ($regExit -eq 1)
{
Write-Host 'Key present but writeback not enabled.'
exit 51
}
# Exit if GPO not present
elseif ($regExit -eq 5)
{
Write-Host 'GPO not present.'
exit 55
}
# Test for device already being encrypted
elseif ((Test-TBBitlockerStatus -eq True) -and ($regExit -eq 0))
{
Write-Host 'Writeback enabled but device already protected.'
exit 61
}
# Test for device being encrypted and writeback is not enabled.
elseif ((Test-TBBitlockerStatus -eq True) -and ($regExit -ge 1))
{
Write-Host 'Device already protected but AD writeback is not enabled.'
exit 65
}
elseif ((Test-TBBitlockerStatus -eq False) -and ($regExit -eq 0))
{
Enable-TBBitlocker | Write-Output
Write-Host 'Encryption successful.'
exit 10
}
The Write-Host comments are so I can see where it's failing or succeeding. If I manually change the key value to 1 or 0, or encrypt the device and run the script the correct loop runs, however when the device is not encrypted and has the key in the registry, it does absolutely nothing. The exit code doesn't change, there's no output to the host.
I have the output included before the main if scope to see if the values are being set correctly, which they are but that final encryption function loop does not run at all.

Script to search several servers for connected client IP addresses

I'm trying to make a Powershell script to search 5 servers for connected client's IP addresses. There are 5 servers and clients are connected via a user tunnel and an asset one. I'm trying to make a looping script that asks for the asset number and username then searches all 5 servers then reports back the tunnel IPs.
My Powershell skills are very rudimentary. I've managed to make a script that mostly works, the trouble I seem to be having is getting the script to report negative results properly. Here's where I am so far:
Clear-Host
$continue = $true
while ($continue){
Write-Host "Tunnel IP finder" -ForegroundColor White
$Asset = Read-Host "Enter asset number"
$AssetAddress = "$asset.corporate.domain.com"
$User = Read-Host "Enter Username"
$Username = "$User#domain.com"
$servers = "RRAS_01","RRAS_02","RRAS_03","RRAS_04","RRAS_05"
Write-Host ""
$data1 = Foreach ($Server1 in $Servers)
{
Get-RemoteAccessConnectionStatistics -computername $Server1 | Where {$_.UserName -eq $AssetAddress} | Select ClientIPAddress | findstr /r "[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*"
}
foreach($item1 in $data1){
if($item1.length -gt 1){
Write-Host "Asset tunnel IP is $item1"-ForegroundColor Green}
}
if($item1.length -LT 1){
Write-Host "Unable to locate asset on RRAS servers"-ForegroundColor yellow
}
$data2 = Foreach ($Server2 in $Servers)
{
Get-RemoteAccessConnectionStatistics -computername $Server2 | Where {$_.UserName -eq $Username} | Select ClientIPAddress | findstr /r "[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*"
}
foreach($item2 in $data2){
if($item2.length -gt 1){
Write-Host "User tunnel IP is $item2"-ForegroundColor Green}
}
if($item2.length -lt 1){
Write-Host "Unable to locate user on RRAS servers"-ForegroundColor yellow
}
Write-Host ""
}
When I search for an asset number and username of someone who is connected it is reporting the results back like this:
Tunnel IP finder
Enter asset number: N02312
Enter Username: SmithJ
Asset tunnel IP is 10.120.xxx.xxx
User tunnel IP is 10.120.xxx.xxx
AOVPN Tunnel IP finder
Enter asset number:
Which is what I was looking to achieve, it displays the IPs and loops to enter more if needed, however when I input details that is not currently connected I get:
Tunnel IP finder
Enter asset number:
Enter Username:
AOVPN Tunnel IP finder
Enter asset number:
It's not reporting the negative results. When I take the scripting off and just have it dump what it thinks $item1/2 is supposed to be it prints nothing, so as far as I can tell $item1/2.length -LT 1 should be doing it.
I've tried experimenting with Else and Elseif, but I can't seem to make those work. There are probably better ways of doing this, but my Powershell is still very basic.
Any help would be greatly appreciated.
Thanks in advance.
It seems if you loop through a collection that has $null value the $item.length will be equal to 1, not to 0 and its value will not be equal to $null. Maybe that´s what happening to you.
PS C:\Windows\System32\WindowsPowerShell\v1.0> $x = $null
PS C:\Windows\System32\WindowsPowerShell\v1.0> $x.length
0
PS C:\Windows\System32\WindowsPowerShell\v1.0> $arr = {$null}
PS C:\Windows\System32\WindowsPowerShell\v1.0> $arr.length
1
PS C:\Windows\System32\WindowsPowerShell\v1.0> $arr | % {$_.length}
1
PS C:\Windows\System32\WindowsPowerShell\v1.0> $arr | % {$_ -eq $null}
False
In your code you only check for the length to be either less or greater than 1 so in the case of the length being equal to 1 it would not print anything. Since the $null check doesnt work I think the best option would be to change the second comparison operator to less or equal instead of less than:
if($item1.length -LE 1){
Write-Host "Unable to locate asset on RRAS servers"-ForegroundColor yellow
}

Powershell / .net reflection - GetMethod() finds a normal method, but not a generic one - why?

Setup: on Windows 10 / PS 5.1, run this to get access to the WindowsRuntime and the WinRT types I'm using:
Add-Type -AssemblyName System.Runtime.WindowsRuntime
[Windows.Foundation.IAsyncAction,Windows.Foundation,ContentType=WindowsRuntime]
[Windows.Foundation.IAsyncOperation`1,Windows.Foundation,ContentType=WindowsRuntime]
[Windows.Foundation.IAsyncOperationWithProgress`2,Windows.Foundation,ContentType=WindowsRuntime]
OK, now find an extension method I'm not looking for - AsTask() which takes one parameter, typed as an [IAsyncAction]:
[System.WindowsRuntimeSystemExtensions].GetMethod('AsTask',
[Windows.Foundation.IAsyncAction])
There will be some output - a method found.
Now try for the one I am looking for, the same AsTask() method, but this time the overload which takes a parameter typed IAsyncOperation<T> or [IAsyncOperation`1]:
[System.WindowsRuntimeSystemExtensions].GetMethod('AsTask',
[Windows.Foundation.IAsyncOperation`1])
No output. No output if the type name is given as a string instead.
But that overload does exist; ask for all the methods and filter them afterwards, and this will find it:
([System.WindowsRuntimeSystemExtensions].GetMethods() |
Where-Object { $_.Name -eq 'AsTask' -and $_.GetParameters().Count -eq 1 -and
$_.GetParameters()[0].ParameterType.Name -eq 'IAsyncOperation`1' })[0]
This last block of code is what I am using and it works, the question which led me here was: can I ask for that method directly from GetMethod() in one call?
The types from the generic "AsTask"-method and [Windows.Foundation.IAsyncOperation`1] are not the same even if they have the same type-GUID. They are different in 4 parameters, but they are read-only:
Add-Type -AssemblyName System.Runtime.WindowsRuntime
$methods = [System.WindowsRuntimeSystemExtensions].GetMethods()
$taskList = $methods | ?{$_.Name -eq "AsTask"}
$asTask = $taskList | ?{$_.GetParameters().Count -eq 1 -and $_.GetParameters()[0].ParameterType.Name -eq 'IAsyncOperation`1' }
$type1 = $asTask.GetParameters().ParameterType
$type2 = [Windows.Foundation.IAsyncOperation`1]
$attribList = ("IsGenericTypeDefinition", "IsConstructedGenericType", "GenericTypeParameters", "GenericTypeArguments")
foreach ($attrib in $attribList) {
"$attrib : $($type1.$attrib) -> $($type2.$attrib)"
}

Missing AD module and can't get it, need something similar or something to simulate it

So I'm trying to output a complete KB list for all computers on a server (which works on one computer) but it doesn't recognize Get-ADcomputer as a cmdlet. When checking various sources, it appears that the AD module isn't included. As I'm doing this on a work computer/server I'm hesitant to download anything or anything of that nature.
Is there any way I can achieve the following without using the AD module or someway I might be missing how to import the module (if it exists, which I don't think it does on this system)?
# 1. Define credentials
$cred = Get-Credential
# 2. Define a scriptblock
$sb = {
$Session = New-Object -ComObject Microsoft.Update.Session
$Searcher = $Session.CreateUpdateSearcher()
$HistoryCount = $Searcher.GetTotalHistoryCount()
$Searcher.QueryHistory(0,$HistoryCount) | ForEach-Object -Process {
$Title = $null
if ($_.Title -match "\(KB\d{6,7}\)") {
# Split returns an array of strings
$Title = ($_.Title -split '.*\((?<KB>KB\d{6,7})\)')[1]
} else {
$Title = $_.Title
}
$Result = $null
switch ($_.ResultCode) {
0 { $Result = 'NotStarted'}
1 { $Result = 'InProgress' }
2 { $Result = 'Succeeded' }
3 { $Result = 'SucceededWithErrors' }
4 { $Result = 'Failed' }
5 { $Result = 'Aborted' }
default { $Result = $_ }
}
New-Object -TypeName PSObject -Property #{
InstalledOn = Get-Date -Date $_.Date;
Title = $Title;
Name = $_.Title;
Status = $Result
}
} | Sort-Object -Descending:$false -Property InstalledOn | Where {
$_.Title -notmatch "^Definition\sUpdate"
}
}
#Get all servers in your AD (if less than 10000)
Get-ADComputer -ResultPageSize 10000 -SearchScope Subtree -Filter {
(OperatingSystem -like "Windows*Server*")
} | ForEach-Object {
# Get the computername from the AD object
$computer = $_.Name
# Create a hash table for splatting
$HT = #{
ComputerName = $computer ;
ScriptBlock = $sb ;
Credential = $cred;
ErrorAction = "Stop";
}
# Execute the code on remote computers
try {
Invoke-Command #HT
} catch {
Write-Warning -Message "Failed to execute on $computer because $($_.Exception.Message)"
}
} | Format-Table PSComputerName,Title,Status,InstalledOn,Name -AutoSize
You've got 3 options:
First is to just install the RSAT feature for AD which will include the AD module. This is probably the best option unless there is something specific preventing it. If you're running your script from a client operating systems you need to install the RSAT first, though.
Option 2 (which should only be used if adding the Windows feature is somehow an issue) is to download and use the Quest AD tools, which give very similar functionality, but it looks like Dell is doing their best to hide these now so that may be difficult to locate...
Option 3 is to use the .NET ADSI classes to access AD directly, which will work without any additional downloads on any system capable of running PowerShell. If you'd like to go this route you should check out the documentation for the interface Here and for the System.DirectoryServices namespace Here.
Edit
Just noticed the last part of your question, what do you mean by "a complete KB list"? Not just Windows updates or things updated manually or whatever? What else would be in a list of Windows updates that was not a Windows update?
You have not mentioned the OSes you are using but in general if you have a server 2008 R2 or above, all you have to do it activate the RSAT feature AD PowerShell Module and you will have the cmdlet you are looking for.
On a client machine, you 'have to' install RSAT, and then activate the features. You can take a look at the technet article for more info: https://technet.microsoft.com/en-us/library/ee449483(v=ws.10).aspx
If you don't want to use that option, then you will have to use .NET ADSI classes. There are tons of examples on how to do this, it basically boils down to a couple of lines really. Technet has examples on this as well: https://technet.microsoft.com/en-us/library/ff730967.aspx

Powershell Function returning blank data when setting a variable

First off, I appoligize for my terminalogy. I self taught myself powershell, so my lingo may be a bit off.
I have a script that I am running in WinPE which brings up a menu system for windows imaging (selecting image file, running a diskpart file, ect.). Part of this script looks up which volumes are visible to windows PE and prompts the user to select which volume they would like to install the image to. I separated this portion of the code into it's own function. The issue I'm having is that the WMI call to lookup the logical disks returns blank results when the function is setting a variable (see example below). But when I dot-source and run the function by itself it works fine. Here is the code i'm using
$Vol = Select-VolumeLetter
---- In a seperate file which has been dot-sourced -----
Function Select-VolumeLetter {
$InstallVolumes = (gwmi Win32_LogicalDisk -namespace "root\CIMV2")
$i = 1
Write-Host "`nPlease Select a volume to install the OS on:`n"
ForEach ($V in $InstallVolumes) {
Write-Host "[$i]"
$V | Select Name, Size, FileSystem, VolumeName | fl
$i++
}
Do{
$Range = $i - 1
$Input = Read-Host "Choose 1-$Range"
$Result = ""
if(![int32]::TryParse( $Input , [ref]$Result )) {Write-Host "`nInvalid selection. Please try again.`n"}
} until ($Result -gt 0 -and $Result -lt ($i + 1))
$InstallVolumes[($Input -1)].Name
}
The result looks like:
[1]
[2]
[3] ect.
But when run just as a function I get the full WMI information requested in the correct place. Any help would be much appreciated.
So I think you have some terminology wrong. What you show is not the results, that it what appears on your screen due to the Write-Host calls. You are not seeing the WMI information because you are not specifying that it be written to the screen and it is therefore being passed back as a result of the function. In your case $Vol will contain that WMI information. I think what would fix your situation would be something like this:
$InstallVolumes = (gwmi Win32_LogicalDisk -namespace "root\CIMV2")
$i = 1
Write-Host "`nPlease Select a volume to install the OS on:"
ForEach ($V in $InstallVolumes) {
Write-Host "`n[$i]"
Write-Host ($V | Select Name, Size, FileSystem, VolumeName | fl|out-string).trim()
$i++
}