Let’s dive deep into the CallerMemberName attribute and explore its usage from multiple angles. We’ll see various methods of invoking it, shedding light on how it is defined at compile time.
Table of Contents
Just a second! 🫷 If you are here, it means that you are a software developer.
So, you know that storage, networking, and domain management have a cost .
If you want to support this blog, please ensure that you have disabled the adblocker for this site. I configured Google AdSense to show as few ADS as possible – I don’t want to bother you with lots of ads, but I still need to add some to pay for the resources for my site.
Thank you for your understanding. – Davide
Method names change. And, if you are using method names in some places specifying them manually, you’ll spend a lot of time updating them.
Luckily for us, in C#, we can use an attribute named CallerMemberName.
This attribute can be applied to a parameter in the method signature so that its runtime value is the caller method’s name.
publicvoid SayMyName([CallerMemberName] string? methodName = null) =>
Console.WriteLine($"The method name is {methodName ?? "NULL"}!");
It’s important to note that the parameter must be a nullable string: this way if the caller sets its value, the actual value is set. Otherwise, the name of the caller method is used. Well, if the caller method has a name! 👀
Getting the caller method’s name via direct execution
Direct call with overridden name:
The method name is Walter White!
It’s important to note that the compiler sets the methodName parameter only if it is not otherwise specified.
This means that if you call SayMyName(null), the value will be null – because you explicitly declared the value.
privatevoid DirectCallWithNullName()
{
Console.WriteLine("Direct call with null name:");
SayMyName(null);
}
The printed text is then:
Direct call with null name:
The method name is NULL!
CallerMemberName when the method is called via an Action
Let’s see what happens when calling it via an Action:
publicvoid CallViaAction()
{
Console.WriteLine("Calling via Action:");
Action<int> action = (_) => SayMyName();
var singleElement = new List<int> { 1 };
singleElement.ForEach(s => action(s));
}
This method prints this text:
Calling via Action:
The method name is CallViaAction!
Now, things get interesting: the CallerMemberName attribute recognizes the method’s name that contains the overall expression, not just the actual caller.
We can see that, syntactically, the caller is the ForEach method (which is a method of the List<T> class). But, in the final result, the ForEach method is ignored, as the method is actually called by the CallViaAction method.
This can be verified by accessing the compiler-generated code, for example by using Sharplab.
At compile time, since no value is passed to the SayMyName method, it gets autopopulated with the parent method name. Then, the ForEach method calls SayMyName, but the methodName is already defined at compiled time.
Lambda executions and the CallerMemberName attribute
What if we try to execute the SayMyName method by accessing the root class (in this case, CallerMemberNameTests) as a dynamic type?
privatevoid CallViaDynamicInvocation()
{
Console.WriteLine("Calling via dynamic invocation:");
dynamic dynamicInstance = new CallerMemberNameTests(null);
dynamicInstance.SayMyName();
}
Oddly enough, the attribute does not work as could have expected, but it prints NULL:
Calling via dynamic invocation:
The method name is NULL!
This happens because, at compile time, there is no reference to the caller method.
privatevoid CallViaDynamicInvocation()
{
Console.WriteLine("Calling via dynamic invocation:");
object arg = new C();
if (<>o__0.<>p__0 == null)
{
Type typeFromHandle = typeof(C);
CSharpArgumentInfo[] array = new CSharpArgumentInfo[1];
array[0] = CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null);
<>o__0.<>p__0 = CallSite<Action<CallSite, object>>.Create(Microsoft.CSharp.RuntimeBinder.Binder.InvokeMember(CSharpBinderFlags.ResultDiscarded, "SayMyName", null, typeFromHandle, array));
}
<>o__0.<>p__0.Target(<>o__0.<>p__0, arg);
}
I have to admit that I don’t understand why this happens: if you want, drop a comment to explain to us what is going on, I’d love to learn more about it! 📩
Event handlers can get the method name
Then, we have custom events.
We define events in one place, but they are executed indirectly.
privatevoid CallViaEventHandler()
{
Console.WriteLine("Calling via events:");
var eventSource = new MyEventClass();
eventSource.MyEvent += (sender, e) => SayMyName();
eventSource.TriggerEvent();
}
publicclassMyEventClass{
publicevent EventHandler MyEvent;
publicvoid TriggerEvent() =>
// Raises an event which in our case calls SayMyName via subscribing lambda method MyEvent?.Invoke(this, EventArgs.Empty);
}
So, what will the result be? “Who” is the caller of this method?
Calling via events:
The method name is CallViaEventHandler!
Again, it all boils down to how the method is generated at compile time: even if the actual execution is performed “asynchronously” – I know, it’s not the most obvious word for this case – at compile time the method is declared by the CallViaEventHandler method.
CallerMemberName from the Class constructor
Lastly, what happens when we call it from the constructor?
public CallerMemberNameTests(IOutput output) : base(output)
{
Console.WriteLine("Calling from the constructor");
SayMyName();
}
We can consider constructors to be a special kind of method, but what’s in their names? What can we find?
Calling from the constructor
The method name is .ctor!
Yes, the actual method name is .ctor! Regardless of the class name, the constructor is considered to be a method with that specific internal name.
Wrapping up
In this article, we started from a “simple” topic but learned a few things about how code is compiled and the differences between runtime and compile time.
Seqrite Labs APT team has uncovered new tactics of Pakistan-linked SideCopy APT deployed since the last week of December 2024. The group has expanded its scope of targeting beyond Indian government, defence, maritime sectors, and university students to now include entities under railway, oil & gas, and external affairs ministries. One notable shift in recent campaigns is the transition from using HTML Application (HTA) files to adopting Microsoft Installer (MSI) packages as a primary staging mechanism.
Threat actors are continuously evolving their tactics to evade detection, and this shift is driven by their persistent use of DLL side-loading and multi-platform intrusions. This evolution also incorporates techniques such as reflective loading and repurposing open-source tools such as Xeno RAT and Spark RAT, following its trend with Async RAT to extend its capabilities. Additionally, a new payload dubbed CurlBack RAT has been identified that registers the victim with the C2 server.
Key Findings
Usernames associated with attacker email IDs are impersonating a government personnel member with cyber security background, utilizing compromised IDs.
A fake domain mimicking an e-governance service, with an open directory, is used to host payloads and credential phishing login pages.
Thirteen sub-domains and URLs host login pages for various RTS Services for multiple City Municipal Corporations (CMCs), all in the state of Maharashtra.
The official domain of National Hydrology Project (NHP), under the Ministry of Water Resources, has been compromised to deliver malicious payloads.
New tactics such as reflective loading and AES decryption of resource section via PowerShell to deploy a custom version of C#-based open-source tool XenoRAT.
A modified variant of Golang-based open-source tool SparkRAT, is targeting Linux platforms, has been deployed via the same stager previously used for Poseidon and Ares RAT payloads.
A new RAT dubbed CurlBack utilizing DLL side-loading technique is used. It registers the victim with C2 server via UUID and supports file transfer using curl.
Honey-trap themed campaigns were observed in January 2025 and June 2024, coinciding with the arrest of a government employee accused of leaking sensitive data to a Pakistani handler.
A previously compromised education portal seen in Aug 2024, became active again in February 2025 with new URLs targeting university students. These employ three different themes: “Climate Change”, “Research Work”, and “Professional” (Complete analysis can be viewed in the recording here, explaining six different clusters of SideCopy APT).
The parent group of SideCopy, APT36, has targeted Afghanistan after a long with a theme related to Office of the Prisoners Administration (OPA) under Islamic Emirate of Afghanistan. A recent campaign targeting Linux systems with the theme “Developing Leadership for Future Wars” involves AES/RC4 encrypted stagers to drop MeshAgent RMM tool.
Targeted sectors under the Indian Ministry
Railways
Oil & Gas
External Affairs
Defence
Phishing Emails
The campaign targeting the Defence sector beings with a phishing email dated 13 January 2025, with the subject “Update schedule for NDC 65 as discussed”. The email contains a link to download a file named “NDC65-Updated-Schedule.pdf” to lure the target.
Fig. 1 – NDC Phishing Email (1)
A second phishing email sent on 15 January 2025 with the subject “Policy update for this course.txt”, also contains a phishing link. This email originates from an official-looking email ID which is likely compromised. National Defence College (NDC) is a defence service training institute for strategic and practice of National Security located in Delhi, operates under the Ministry of Defence, India.
Fig. 2 – NDC Phishing Email (2)
The attacker’s email address “gsosystems-ndc@outlook[.]com”, was created on 10 January 2025 in UAE and was last seen active on 28 February 2025. OSINT reveals similar looking email ID “gsosystems.ndc-mod@nic[.]in” belonging to National Informatics Centre (NIC), a department under the Ministry of Electronics and Information Technology (MeitY), India. The username linked to the attacker’s email impersonates a government personnel member with cyber security background.
Fig. 3 – Attacker Email
Decoy Documents
The decoy is related to the National Defence College (NDC) in India and contains the Annual Training Calendar (Study & Activities) for the year 2025 for the 65th Course (NDC-65). Located in New Delhi, it is the defence service training institute and highest seat of strategic learning for officers of the Defence Service (Indian Armed Forces) and the Civil Services, all operating under the Ministry of Defence, India.
Fig. 4 – NDC Calendar Decoy [Defence]
Another phishing archive file observed with name “2024-National-Holidays-RH-PER_N-1.zip”, comes in two different variants targeting either Windows or Linux systems. Once the payload is triggered, it leads to a decoy document that contains a list of holidays for the Open Line staff for the year 2024 as the name suggests. This is an official notice from Southern Railway dated 19 December 2023, specifically for the Chennai Division. Southern Railway (SR) is one of the eighteen zones of Indian Railways, a state-owned undertaking of the Ministry of Railways, India.
Fig. 5 – Holiday List Decoy [Railways]
The third infection chain includes a document titled “Cybersecurity Guidelines” for the year 2024, which appears to be issued by Hindustan Petroleum Corporation Limited (HPCL). Headquarted in Mumbai, HPCL is a public sector undertaking in petroleum and natural gas industry and is a subsidiary of the Oil and Natural Gas Corporation (ONGC), a state-owned undertaking of the Ministry of Petroleum and Natural Gas, India.
Another document linked to the same infection is the “Pharmaceutical Product Catalogue” for 2025, issued by MAPRA. It is specifically intended for employees of the Ministry of External Affairs (MEA), in India. Mapra Laboratories Pvt. Ltd. is a pharmaceutical company with headquarters in Mumbai.
Fig. 7 – Catalogue Decoy [External Affairs]
OpenDir and CredPhish
A fake domain impersonating the e-Governance portal services has been utilized to carry out the campaign targeting railway entities. This domain was created on 16 June 2023 and features an open directory hosting multiple files, identified during the investigation.
Fig. 8 – Open directory
A total of 13 sub-domains have been identified, which function as login portals for various systems such as:
Webmail
Safety Tank Management System
Payroll System
Set Authority
These are likely used for credential phishing, actively impersonating multiple legitimate government portals since last year. These login pages are typically associated with RTS Services (Right to Public Services Act) and cater to various City Municipal Corporations (CMC). All these fake portals belong to cities located within the state of Maharashtra:
Chandrapur
Gadchiroli
Akola
Satara
Vasai Virar
Ballarpur
Mira Bhaindar
Fig. 9 – Login portals hosted on fake domain
The following table lists the identified sub-domains and the dates they were first observed:
Sub-domains
First Seen
gadchiroli.egovservice[.]in
2024-12-16
pen.egovservice[.]in
2024-11-27
cpcontacts.egovservice[.]in
cpanel.egovservice[.]in
webdisk.egovservice[.]in
cpcalendars.egovservice[.]in
webmail.egovservice[.]in
2024-01-03
dss.egovservice[.]in
cmc.egovservice[.]in
2023-11-03
mail.egovservice[.]in
2023-10-13
pakola.egovservice[.]in
pakora.egovservice[.]in
2023-07-23
egovservice[.]in
2023-06-16
All these domains have the following DNS history primarily registered under AS 140641 (YOTTA NETWORK SERVICES PRIVATE LIMITED). This indicates a possible coordinated infrastructure set up to impersonate legitimate services and collect credentials from unsuspecting users.
Fig. 10 – DNS history
Further investigation into the open directory revealed additional URLs associated with the fake domain. These URLs likely serve similar phishing purposes and host further decoy content.
hxxps://egovservice.in/vvcmcrts/
hxxps://egovservice.in/vvcmc_safety_tank/
hxxps://egovservice.in/testformonline/test_form
hxxps://egovservice.in/payroll_vvcmc/
hxxps://egovservice.in/pakora/egovservice.in/
hxxps://egovservice.in/dssrts/
hxxps://egovservice.in/cmc/
hxxps://egovservice.in/vvcmcrtsballarpur72/
hxxps://egovservice.in/dss/
hxxps://egovservice.in/130521/set_authority/
hxxps://egovservice.in/130521/13/
Cluster-A
The first cluster of SideCopy’s operations shows a sophisticated approach by simultaneously targeting both Windows and Linux environments. New remote access trojans (RATs) have been added to their arsenal, enhancing their capability to compromise diverse systems effectively.
Fig. 11 – Cluster A
Windows
A spear-phishing email link downloads an archive file, that contains double extension (.pdf.lnk) shortcut. They are hosted on domains that look to be legitimate:
The shortcut triggers cmd.exe with arguments that utilize escape characters (^) to evade detection and reduce readability. A new machine ID “dv-kevin” is seen with these files as we see “desktop-” prefix in its place usually.
Fig. 12 – Shortcuts with double extension
Utility msiexec.exe is used for installing the MSI packages that are hosted remotely. It uses quiet mode flag with the installation switch.
The first domain mimics a fake e-governance site seen with the open directory, while the second one is a compromised domain that belongs to the official National Hydrology Project, an entity under the Ministry of Water Resources. The MSI contains a .NET executable ConsoleApp1.exe which drops multiple PE files that are base64 encoded. Firstly, the decoy document is dropped in Public directory and opened, whereas remaining PE files are dropped in ‘C:\ProgramData\LavaSoft\’. Among them are two DLLs:
Legitimate DLL: Sampeose.dll
Malicious DLL: DUI70.dll, identified as CurlBack RAT.
Fig. 13 – Dropper within MSI package
CurlBack RAT
A signed Windows binary girbesre.exe with original name CameraSettingsUIHost.exe is dropped beside the DLLs. Upon execution, the EXE side-loads the malicious DLL. Persistence is achieved by dropping a HTA script (svnides.hta) that creates a Run registry key for the EXE. Two different malicious DLL samples were found, which have the compilation timestamps as 2024-12-24 and 2024-12-30.
Fig. 14 – Checking response ‘/antivmcommand’
CurlBack RAT initially checks the response of a specific URL with the command ‘/antivmcommand’. If the response is “on”, it proceeds, otherwise it terminates itself thereby maintaining a check. It gathers system information, and any connected USB devices using the registry key:
“SYSTEM\\ControlSet001\\Enum\\USBSTOR”
Fig. 15 – Retrieving system info and USB devices
Displays connected and running processes are enumerated to check for explorer, msedge, chrome, notepad, taskmgr, services, defender, and settings.
Fig. 16 – Enumerate displays and processes
Next, it generates a UUID for client registration with the C2 server. The ID generated is dumped at “C:\Users\<username>\.client_id.txt” along with the username.
Fig. 17 – Client ID generated for C2 registration
Before registering with the ID, persistence is set up via scheduled task with the name “OneDrive” for the legitimate binary, which can be observed at the location: “C:\Windows\System32\Tasks\OneDrive”.
Fig. 18 – Scheduled Task
Reversed strings appended to the C2 domain and their purpose:
String
Functionality
/retsiger/
Register client with the C2
/sdnammoc/
Fetch commands from C2
/taebtraeh/
Check connection with C2 regularly
/stluser/
Upload results to the C2
Once registered, the connection is kept alive to retrieve any commands that are returned in the response.
Fig. 19 – Commands response after registration
If the response contains any value, it retrieves the current timestamp and executes one of the following C2 commands:
Command
Functionality
info
Gather system information
download
Download files from the host
persistence
Modify persistence settings
run
Execute arbitrary commands
extract
Extract data from the system
permission
Check and elevate privileges
users
Enumerate user accounts
cmd
Execute command-line operations
Fig. 20 – Checking process privilege with ‘permission’ command
Other basic functions include fetching user and host details, extracting archive files, and creating tasks. Strings and code show that CURL within the malicious DLL is present to enumerate and transfer various file formats:
Image files: GIF, JPEG, JPG, SVG
Text files: TXT, HTML, PDF, XML
Fig. 21 – CURL protocols supported
Linux
In addition to its Windows-focused attacks, the first cluster of SideCopy also targets Linux environments. The malicious archive file shares the same name as its Windows counterpart, but with a modification date of 2024-12-20. This archive contains a Go-based ELF binary, reflecting a consistent cross-platform strategy. Upon analysis, the function flow of the stager has code similarity to the stagers associated with Poseidon and Ares RAT. These are linked to Transparent Tribe and SideCopy APTs respectively.
Fig. 22 – Golang Stager for Linux
Stager functionality:
Uses wget command to download a decoy from egovservice domain into the target directory /.local/share and open it (National-Holidays-RH-PER_N-1.pdf).
Download the final payload elf as /.local/share/xdg-open and execute.
Create a crontab ‘/dev/shm/mycron’ to maintain persistence through system reboot for the payload, under the current username.
The final payload delivered by the stager is Spark RAT, an open-source remote access trojan with cross-platform support for Windows, macOS, and Linux systems. Written in Golang and released on GitHub in 2022, the RAT is very popular with over 500 forks. Spark RAT uses WebSocket protocol and HTTP requests to communicate with the C2 server.
Fig. 23 – Custom Spark RAT ‘thunder’ connecting to C2
Features of Spark RAT include process management and termination, network traffic monitoring, file exploration and transfer, file editing and deletion, code highlighting, desktop monitoring, screenshot capture, OS information retrieval, and remote terminal access. Additionally, it supports power management functions like shutdown, reboot, log-off, sleep, hibernate and lock screen functions.
Cluster-B
The second cluster of SideCopy’s activities targets Windows systems, although we suspect that it is targeting Linux systems based on their infrastructure observed since 2023.
Fig. 24 – Cluster B
The infection starts with a spear-phishing email link, that downloads an archive file named ‘NDC65-Updated-Schedule.zip’. This contains a shortcut file in double extension format which triggers a remote HTA file hosted on another compromised domain:
The machine ID associated with the LNK “desktop-ey8nc5b” has been observed in previous campaigns of SideCopy, although the modification date ‘2023:05:26’ suggests it may be an older one being reused. In parallel to the MSI stagers, the group continues to utilize HTA-based stagers which remain almost fully undetected (FUD).
Fig. 26 – Almost FUD stager of HTA
The HTA file contains a Base64 encoded .NET payload BroaderAspect.dll, which is decoded and loaded directly into the memory of MSHTA. This binary opens the dropped NDC decoy document in ProgramData directory and an addtional .NET stager as a PDF in the Public directory. Persistence is set via Run registry key with the name “Edgre” and executes as:
The dropped .NET binary named ‘Myapp.pdb’ has two resource files:
“Myapp.Resources.Document.pdf”
“Myapp.Properties.Resources.resources”
The first one is decoded using Caesar cipher with shift of 9 characters in backward direction. It is dropped as ‘Public\Downloads\Document.pdf’ (122.98 KB), which is a 2004 GIAC Paper on “Advanced communication techniques of remote access trojan horses on windows operating systems”.
Fig. 27– Document with appended payload
Though it is not a decoy, an encrypted payload is appended at the end. The malware searches for the “%%EOF” marker to separate PDF data from EXE data. The PDF data is extracted from the start to the marker, while the EXE Data is extracted after skipping 6 bytes beyond the marker.
Fig. 28 – Extracting EXE after EOF marker
After some delay, the EXE data is dropped as “Public\Downloads\suport.exe” (49.53 KB) which is sent as an argument along with a key to trigger a PowerShell command.
Fig. 29 – Extracting resource and triggering PowerShell
PowerShell Stage
The execution of PowerShell command with basic arguments “-NoProfile -ExecutionPolicy Bypass -Command” to ignore policies and profile is seen. Two parameters are sent:
After some delay, the encryption key is decoded from Base64, and the first 16 bytes are treated as the IV for AES encryption (CBC mode with PKCS7 padding). This is done to load the decrypted binary as a .NET assembly directly into memory, invoking its entry point.
Fig. 30 – PowerShell decryption
Custom Xeno RAT
Dumping the final .NET payload named ‘DevApp.exe’ leads us to familiar functions seen in Xeno RAT. It is an open source remote access trojan that was first seen at the end of 2023. Key features include HVNC, live microphone access, socks5 reverse proxy, UAC bypass, keylogger, and more. The custom variant used by SideCopy has added basic string manipulation methods with C2 and port as 79.141.161[.]58:1256.
Fig. 31 – Custom Xeno RAT
Last year, a custom Xeno RAT variant named MoonPeak was used by a North Korean-linked APT tracked as UAT-5394. Similarly, custom Spark RAT variants have been adopted by Chinese-speaking actors such as DragonSpark and TAG-100.
Infrastructure and Attribution
Domains used for malware staging by the threat group. Most of them have registrar as GoDaddy.com, LLC.
Staging Domain
First Seen
Created
ASN
modspaceinterior[.]com
Jan 2025
Sept 2024
AS 46606 – GoDaddy
drjagrutichavan[.]com
Jan 2025
Oct 2021
AS 394695 – GoDaddy
nhp.mowr[.]gov[.]in
Dec 2024
Feb 2005
AS 4758 – National Informatics Centre
egovservice[.]in
Dec 2024
June 2023
AS 140641 – GoDaddy
pmshriggssssiwan[.]in
Nov 2024
Mar 2024
AS 47583 – Hostinger
educationportals[.]in
Aug 2024
Aug 2024
AS 22612 – NameCheap
C2 domains have been created just before the campaign in the last week of December 2024. With Canadian registrar “Internet Domain Service BS Corp.”, they resolve to IPs with Cloudflare ASN 13335 located in California.
C2 Domain
Created
IP
ASN
updates.widgetservicecenter[.]com
2024-Dec-25
104.21.15[.]163
172.67.163[.]31
ASN 13335 – Clouflare
updates.biossysinternal[.]com
2024-Dec-23
172.67.167[.]230
104.21.13[.]17
ASN 202015 – HZ Hosting Ltd.
The C2 for Xeno RAT 79.141.161[.]58 has a unique common name (CN=PACKERP-63KUN8U) with HZ Hosting Limited of ASN 202015. The port used for communication is 1256 but an open RDP port 56777 is also observed.
Fig. 32 – Diamond Model
Both C2 domains are associated with Cloudflare ASN 13335, resolved to IP range 172.67.xx.xx. Similar C2 domains on this ASN have previously been leveraged by SideCopy in attacks targeting the maritime sector. Considering the past infection clusters, observed TTPs and hosted open directories, these campaigns with new TTPs are attributed to SideCopy with high confidence.
Conclusion
Pakistan-linked SideCopy APT group has significantly evolved its tactics since late December 2024, expanding its targets to include critical sectors such as railways, oil & gas, and external affairs ministries. The group has shifted from using HTA files to MSI packages as a primary staging mechanism and continues to employ advanced techniques like DLL side-loading, reflective loading, and AES decryption via PowerShell. Additionally, they are leveraging customized open-source tools like Xeno RAT and Spark RAT, along with deploying the newly identified CurlBack RAT. Compromised domains and fake sites are being utilized for credential phishing and payload hosting, highlighting the group’s ongoing efforts to enhance persistence and evade detection.
If you’re used to ASP.NET apps when you think about apps that are JavaScript heavy, “front end apps” or TypeScript focused, it can be confusing as to “where does .NET fit in?”
You need to consider the responsibilities of your various projects or subsystems and the multiple totally valid ways you can build a web site or web app. Let’s consider just a few:
This may have a Web API, Razor Pages, with or without the MVC pattern.
You maybe have just added JavaScript via <script> tags
Maybe you added a script minimizer/minifier task
Can be confusing because it can feel like your app needs to ‘build both the client and the server’ from one project
A mostly JavaScript/TypeScript frontend app where the HTML could be served from any web server (node, kestrel, static web apps, nginx, etc)
This app may use Vue or React or Angular but it’s not an “ASP.NET app”
It calls backend Web APIs that may be served by ASP.NET, Azure Functions, 3rd party REST APIs, or all of the above
This scenario has sometimes been confusing for ASP.NET developers who may get confused about responsibility. Who builds what, where do things end up, how do I build and deploy this?
VS2022 brings JavaScript and TypeScript support into VS with a full JavaScript Language Service based on TS. It provides a TypeScript NuGet Package so you can build your whole app with MSBuild and VS will do the right thing.
NEW: Starting in Visual Studio 2022, there is a new JavaScript/TypeScript project type (.esproj) that allows you to create standalone Angular, React, and Vue projects in Visual Studio.
The .esproj concept is great for folks familiar with Visual Studio as we know that a Solution contains one or more Projects. Visual Studio manages files for a single application in a Project. The project includes source code, resources, and configuration files. In this case we can have a .csproj for a backend Web API and an .esproj that uses a client side template like Angular, React, or Vue.
Thing is, historically when Visual Studio supported Angular, React, or Vue, it’s templates were out of date and not updated enough. VS2022 uses the native CLIs for these front ends, solving that problem with Angular CLI, Create React App, and Vue CLI.
If I am in VS and go “File New Project” there are Standalone templates that solve Example 2 above. I’ll pick JavaScript React.
Then I’ll click “Add integration for Empty ASP.NET Web API. This will give me a frontend with javascript ready to call a ASP.NET Web API backend. I’ll follow along here.
It then uses the React CLI to make the front end, which again, is cool as it’s whatever version I want it to be.
Then I’ll add my ASP.NET Web API backend to the same solution, so now I have an esproj and a csproj like this
Now I have a nice clean two project system – in this case more JavaScript focused than .NET focused. This one uses npm to startup the project using their web development server and proxyMiddleware to proxy localhost:3000 calls over to the ASP.NET Web API project.
Here is a React app served by npm calling over to the Weather service served from Kestrel on ASP.NET.
This is inverted than most ASP.NET Folks are used to, and that’s OK. This shows me that Visual Studio 2022 can support either development style, use the CLI that is installed for whatever Frontend Framework, and allow me to choose what web server and web browser (via Launch.json) I want.
Sponsor: Make login Auth0’s problem. Not yours. Provide the convenient login features your customers want, like social login, multi-factor authentication, single sign-on, passwordless, and more. Get started for free.
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
Home Assistant can quickly become a hobby that overwhelms you. Every object (entity) in your house that is even remotely connected can become programmable. Everything. Even people! You can declare that any name:value pair that (for example) your phone can expose can be consumable by Home Assistant. Questions like “is Scott home” or “what’s Scott’s phone battery” can be associated with Scott the Entity in the Home Assistant Dashboard.
I was amazed at the devices/objects that Home Assistant discovered that it could automate. Lights, remotes, Spotify, and more. You’ll find that any internally connected device you have likely has an Integration available.
Temperature, Light Status, sure, that’s easy Home Automation. But integrations and 3rd party code can give you details like “Is the Living Room dark” or “is there motion in the driveway.” From these building blocks, you can then build your own IFTTT (If This Then That) automations, combining not just two systems, but any and all disparate systems.
What’s the best part? This all runs LOCALLY. Not in a cloud or the cloud or anyone’s cloud. I’ve got my stuff running on a Raspberry Pi 4. Even better I put a Power Over Ethernet (PoE) hat on my Rpi so I have just one network wire into my hub that powers the Pi.
I believe setting up Home Assistant on a Pi is the best and easiest way to get started. That said, you can also run in a Docker Container, on a Synology or other NAS, or just on Windows or Mac in the background. It’s up to you. Optionally, you can pay Nabu Casa $5 for remote (outside your house) network access via transparent forwarding. But to be clear, it all still runs inside your house and not in the cloud.
OK, to the main point. I used to have an Amazon Ring Doorbell that would integrate with Amazon Alexa and when you pressed the doorbell it would say “Someone is at the front door” on our all Alexas. It was a lovely little integration that worked nicely in our lives.
However, I swapped out the Ring for a Unifi Protect G4 Doorbell for a number of reasons. I don’t want to pump video to outside services, so this doorbell integrates nicely with my existing Unifi installation and records video to a local hard drive. However, I lose any Alexa integration and this nice little “someone is at the door” announcement. So this seems like a perfect job for Home Assistant.
This enables 3rd party “untrusted” integrations directly from GitHub. You’ll need a GitHub account and it’ll clone custom integrations directly into your local HA.
I also recommend the Terminal & SSH (9.2.2), File editor (5.3.3) add ons so you can see what’s happening.
NOTE: Unifi Protect support is being promoted in Home Assistant v2022.2 so you won’t need this step soon as it’ll be included.
“The UniFi Protect Integration adds support for retrieving Camera feeds and Sensor data from a UniFi Protect installation on either an Ubiquiti CloudKey+, Ubiquiti UniFi Dream Machine Pro or UniFi Protect Network Video Recorder.”
This makes all your Alexas show up in Home Assistant as “media players” and also allows you to tts (text to speech) to them.
Authenticate and configure this integration.
I recommend going into your Alexa app and making a Multi-room Speaker Group called “everywhere.” Not only because it’s nice to be able to say “play the music everywhere” but you can also target that “Everywhere” group in Home Assistant.
service: notify.alexa_media_everywhere
data:
message: Someone is at the front door, this is a test
data:
type: announce
method: speak
If that works, you know you can automate Alexa and make it say things. Now, go to Configuration, Automation, and Add a new Automation. Here’s mine. I used the UI to create it. Note that your Entity names may be different if you give your front doorbell camera a different name.
Notice the format of Data, it’s name value pairs within a single field’s value.
…but it also exists in a file called Automations.yaml. Note that the “to: ‘on’” trigger is required or you’ll get double announcements, one for each state change in the doorbell.
- id: '1640995128073'
alias: G4 Doorbell Announcement with Alexa
description: G4 Doorbell Announcement with Alexa
trigger:
- platform: state
entity_id: binary_sensor.front_door_doorbell
to: 'on'
condition: []
action:
- service: notify.alexa_media_everywhere
data:
data:
type: announce
method: speak
message: Someone is at the front door
mode: single
It works! There’s a ton of cool stuff I can automate now!
Sponsor: Make login Auth0’s problem. Not yours. Provide the convenient login features your customers want, like social login, multi-factor authentication, single sign-on, passwordless, and more. Get started for free.
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
There are many let’s encrypt automatic tools for azure but I also wanted to see if I could use certbot in wsl to generate a wildcard certificate for the azure Friday website and then upload the resulting certificates to azure app service.
Azure app service ultimately needs a specific format called dot PFX that includes the full certificate path and all intermediates.
Then I generate the cert. You’ll get a nice text UI from certbot and update your DNS as a verification challenge. Change this to make sure it’s two lines, and your domains and subdomains are correct and your paths are correct.
Then upload the cert to the Certificates section of your App Service, under Bring Your Own Cert.
Then under Custom Domains, click Update Binding and select the new cert (with the latest expiration date).
Next step is to make this even more automatic or select a more automated solution but for now, I’ll worry about this in September and it solved my expensive Wildcard Domain issue.
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
I’ve been doing not just Unit Testing for my sites but full on Integration Testing and Browser Automation Testing as early as 2007 with Selenium. Lately, however, I’ve been using the faster and generally more compatible Playwright. It has one API and can test on Windows, Linux, Mac, locally, in a container (headless), in my CI/CD pipeline, on Azure DevOps, or in GitHub Actions.
For me, it’s that last moment of truth to make sure that the site runs completely from end to end.
I can write those Playwright tests in something like TypeScript, and I could launch them with node, but I like running end unit tests and using that test runner and test harness as my jumping off point for my .NET applications. I’m used to right clicking and “run unit tests” or even better, right click and “debug unit tests” in Visual Studio or VS Code. This gets me the benefit of all of the assertions of a full unit testing framework, and all the benefits of using something like Playwright to automate my browser.
In 2018 I was using WebApplicationFactory and some tricky hacks to basically spin up ASP.NET within .NET (at the time) Core 2.1 within the unit tests and then launching Selenium. This was kind of janky and would require to manually start a separate process and manage its life cycle. However, I kept on with this hack for a number of years basically trying to get the Kestrel Web Server to spin up inside of my unit tests.
I’ve recently upgraded my main site and podcast site to .NET 8. Keep in mind that I’ve been moving my websites forward from early early versions of .NET to the most recent versions. The blog is happily running on Linux in a container on .NET 8, but its original code started in 2002 on .NET 1.1.
I love this. Nice and clean. Certainly here we are assuming that we have a URL in that first line, which will be localhost something, and then we assume that our web application has started up on its own.
Here is the setup code that starts my new “web application test builder factory,” yeah, the name is stupid but it’s descriptive. Note the OneTimeSetUp and the OneTimeTearDown. This starts my web app within the context of my TestHost. Note the :0 makes the app find a port which I then, sadly, have to dig out and put into the Url private for use within my Unit Tests. Note that the <Startup> is in fact my Startup class within Startup.cs which hosts my app’s pipeline and Configure and ConfigureServices get setup here so routing all works.
[OneTimeSetUp] public void Setup() { var builder = WebApplicationTestBuilderFactory.CreateBuilder<Startup>();
var startup = new Startup(builder.Environment); builder.WebHost.ConfigureKestrel(o => o.Listen(IPAddress.Loopback, 0)); startup.ConfigureServices(builder.Services); _app = builder.Build();
// listen on any local port (hence the 0) startup.Configure(_app, _app.Configuration); _app.Start();
//you are kidding me Url = _app.Services.GetRequiredService<IServer>().Features.GetRequiredFeature<IServerAddressesFeature>().Addresses.Last(); }
[OneTimeTearDown] public async Task TearDown() { await _app.DisposeAsync(); }
So what horrors are buried in WebApplicationTestBuilderFactory? The first bit is bad and we should fix it for .NET 9. The rest is actually every nice, with a hat tip to David Fowler for his help and guidance! This is the magic and the ick in one small helper class.
public class WebApplicationTestBuilderFactory { public static WebApplicationBuilder CreateBuilder<T>() where T : class { //This ungodly code requires an unused reference to the MvcTesting package that hooks up // MSBuild to create the manifest file that is read here. var testLocation = Path.Combine(AppContext.BaseDirectory, "MvcTestingAppManifest.json"); var json = JsonObject.Parse(File.ReadAllText(testLocation)); var asmFullName = typeof(T).Assembly.FullName ?? throw new InvalidOperationException("Assembly Full Name is null"); var contentRootPath = json?[asmFullName]?.GetValue<string>();
//spin up a real live web application inside TestHost.exe var builder = WebApplication.CreateBuilder( new WebApplicationOptions() { ContentRootPath = contentRootPath, ApplicationName = asmFullName }); return builder; } }
The first 4 lines are nasty. Because the test runs in the context of a different directory and my website needs to run within the context of its own content root path, I have to force the content root path to be correct and the only way to do that is by getting the apps base directory from a file generated within MSBuild from the (aging) MvcTesting package. The package is not used, but by referencing it it gets into the build and makes that file that I then use to pull out the directory.
If we can get rid of that “hack” and pull the directory from context elsewhere, then this helper function turns into a single line and .NET 9 gets WAY WAY more testable!
Now I can run my Unit Tests AND Playwright Browser Integration Tests across all OS’s, headed or headless, in docker or on the metal. The site is updated to .NET 8 and all is right with my code. Well, it runs at least. 😉
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
I use my webcam constantly for streaming and I’m pretty familiar with all the internals and how the camera model on Windows works. I also use OBS extensively, so I regularly use the OBS virtual camera and flow everything through Open Broadcasting Studio.
For my podcast, I use Zencastr which is a web-based app that talks to the webcam via the browser APIs. For YouTubes, I’ll use Riverside or StreamYard, also webapps.
I’ve done this reliably for the last several years without any trouble. Yesterday, I started seeing the most weird thing and it was absolutely perplexing and almost destroyed the day. I started seeing regular pauses in my webcam stream but only in two instances.
The webcam would pause for 10-15 seconds every 90 or so seconds when access the Webcam in a browser
I would see a long pause/hang in OBS when double clicking on my Video Source (Webcam) to view its properties
Micah initially said USB but my usb bus and hubs have worked reliably for years. Thought something might have changed in my El Gato capture device, but that has also been rock solid for 1/2 a decade. Then I started exploring virtual cameras and looked in the windows camera dialog under settings for a list of all virtual cameras.
Interestingly, virtual cameras don’t get listed under Cameras in Settings in Windows:
From what I can tell, there’s no user interface to list out all of your cameras – virtual or otherwise – in windows.
Here’s a quick PowerShell script you can run to list out anything ‘connected’ that also includes the string “cam” in your local devices
Name Manufacturer PNPDeviceID ---- ------------ ----------- Cam Link 4K Microsoft USB\VID_0FD9&PID_0066&MI_00\7&3768531A&0&0000 Digital Audio Interface (2- Cam Link 4K) Microsoft SWD\MMDEVAPI\{0.0.1.00000000}.{AF1690B6-CA2A-4AD3-AAFD-8DDEBB83DD4A} Logitech StreamCam WinUSB Logitech USB\VID_046D&PID_0893&MI_04\7&E36D0CF&0&0004 Logitech StreamCam (Generic USB Audio) USB\VID_046D&PID_0893&MI_02\7&E36D0CF&0&0002 Logitech StreamCam Logitech USB\VID_046D&PID_0893&MI_00\7&E36D0CF&0&0000 Remote Desktop Camera Bus Microsoft UMB\UMB\1&841921D&0&RDCAMERA_BUS Cam Link 4K (Generic USB Audio) USB\VID_0FD9&PID_0066&MI_03\7&3768531A&0&0003 Windows Virtual Camera Device Microsoft SWD\VCAMDEVAPI\B486E21F1D4BC97087EA831093E840AD2177E046699EFBF62B27304F5CCAEF57
However, when I list out my cameras using JavaScript enumerateDevices() like this
// Put variables in global scope to make them available to the browser console. async function listWebcams() { try { const devices = await navigator.mediaDevices.enumerateDevices(); const webcams = devices.filter(device => device.kind === 'videoinput');
Connected webcams:
test.html:11 1. Logitech StreamCam (046d:0893)
test.html:11 2. OBS Virtual Camera (Windows Virtual Camera)
test.html:11 3. Cam Link 4K (0fd9:0066)
test.html:11 4. LSVCam
test.html:11 5. OBS Virtual Camera
So, what, what’s LSVCam? And depending on how I’d call it I’d get the pause and
getUserMedia error: NotReadableError NotReadableError: Could not start video source
Some apps could see this LSVCam and others couldn’t. OBS really dislikes it, browsers really dislike it and it seemed to HANG on enumeration of cameras. Why can parts of Windows see this camera and others can’t?
I don’t know. Do you?
Regardless, it turns that it appears once in my registry, here (this is a dump of the key, you just care about the Registry PATH)
If you want to get rid of it, delete HKEY_CLASSES_ROOT\CLSID\{860BB310-5D01-11d0-BD3B-00A0C911CE86}\Instance\LSVCam
WARNING: DO NOT delete the \Instance, just the LSVCam and below. I am a random person on the internet and you got here by googling, so if you mess up your machine by going into RegEdit.exe, I’m sorry to this man, but it’s above me now.
Where did LSVCam.dll come from, you may ask? TikTok Live Studio, baby. Live Studio Video/Virtual Cam, I am guessing.
Directory of C:\Program Files\TikTok LIVE Studio\0.67.2\resources\app\electron\sdk\lib\MediaSDK_V1
This is a regression that started recently for me, so it’s my opinion that they are installing a virtual camera for their game streaming feature but they are doing it poorly. It’s either not completely installed, or hangs on enumeration, but the result is you’ll see hangs on camera enumeration in your apps, especually browser apps that poll for cameras changes or check on a timer.
Nothing bad will happen if you delete the registry key BUT it’ll show back up when you run TikTok Studio again. I still stream to TikTok, I just delete this key each time until someone on the TikTok Studio development team sees this blog post.
Hope this helps!
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.